|
|
|
@ -1,11 +1,16 @@ |
|
|
|
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/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_swiper_null_safety/flutter_swiper_null_safety.dart'; |
|
|
|
import 'package:get/get.dart'; |
|
|
|
import 'package:tdesign_flutter/tdesign_flutter.dart'; |
|
|
|
|
|
|
|
class LiveGiftPopup extends StatelessWidget { |
|
|
|
class LiveGiftPopup extends StatefulWidget { |
|
|
|
const LiveGiftPopup({ |
|
|
|
super.key, |
|
|
|
required this.activeGift, |
|
|
|
@ -16,9 +21,44 @@ class LiveGiftPopup extends StatelessWidget { |
|
|
|
|
|
|
|
final ValueNotifier<int?> activeGift; |
|
|
|
final ValueNotifier<int> giftNum; |
|
|
|
final List<Map> giftList; |
|
|
|
final List<dynamic> giftList; // 支持 List<Map> 或 List<GiftProductModel> |
|
|
|
final void Function(int) changeActive; |
|
|
|
|
|
|
|
@override |
|
|
|
State<LiveGiftPopup> createState() => _LiveGiftPopupState(); |
|
|
|
} |
|
|
|
|
|
|
|
class _LiveGiftPopupState extends State<LiveGiftPopup> { |
|
|
|
// 选中的用户ID集合 |
|
|
|
final Set<String> _selectedUserIds = <String>{}; |
|
|
|
|
|
|
|
// 切换用户选中状态 |
|
|
|
void _toggleUserSelection(String userId) { |
|
|
|
setState(() { |
|
|
|
if (_selectedUserIds.contains(userId)) { |
|
|
|
_selectedUserIds.remove(userId); |
|
|
|
} else { |
|
|
|
_selectedUserIds.add(userId); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// 全选/取消全选 |
|
|
|
void _toggleSelectAll(List<RtcSeatUserInfo> users) { |
|
|
|
setState(() { |
|
|
|
if (_selectedUserIds.length == users.length) { |
|
|
|
// 如果已全选,则取消全选 |
|
|
|
_selectedUserIds.clear(); |
|
|
|
} else { |
|
|
|
// 否则全选 |
|
|
|
_selectedUserIds.clear(); |
|
|
|
for (var user in users) { |
|
|
|
_selectedUserIds.add(user.userId); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
@override |
|
|
|
Widget build(BuildContext context) { |
|
|
|
return Material( |
|
|
|
@ -32,7 +72,9 @@ class LiveGiftPopup extends StatelessWidget { |
|
|
|
Expanded( |
|
|
|
child: Container( |
|
|
|
decoration: BoxDecoration( |
|
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(9.w)), |
|
|
|
borderRadius: BorderRadius.vertical( |
|
|
|
top: Radius.circular(9.w), |
|
|
|
), |
|
|
|
color: const Color.fromRGBO(22, 19, 28, 1), |
|
|
|
), |
|
|
|
child: Column( |
|
|
|
@ -51,88 +93,166 @@ class LiveGiftPopup extends StatelessWidget { |
|
|
|
} |
|
|
|
|
|
|
|
Widget _buildHeader() { |
|
|
|
return Container( |
|
|
|
height: 53.w, |
|
|
|
padding: EdgeInsets.symmetric(horizontal: 10.w), |
|
|
|
child: Row( |
|
|
|
children: [ |
|
|
|
Row( |
|
|
|
children: [ |
|
|
|
Text( |
|
|
|
"送给: ", |
|
|
|
style: TextStyle(fontSize: 13.w, color: Colors.white), |
|
|
|
), |
|
|
|
SizedBox(width: 6.w), |
|
|
|
...List.generate(3, (index) { |
|
|
|
return Padding( |
|
|
|
padding: EdgeInsets.only(right: 10.w), |
|
|
|
child: Stack( |
|
|
|
children: [ |
|
|
|
ClipRRect( |
|
|
|
borderRadius: BorderRadius.all( |
|
|
|
Radius.circular(index == 0 ? 68.w : 34.w), |
|
|
|
), |
|
|
|
child: Container( |
|
|
|
width: 34.w, |
|
|
|
height: 34.w, |
|
|
|
decoration: BoxDecoration( |
|
|
|
return Obx(() { |
|
|
|
// 获取 RoomController |
|
|
|
final roomController = Get.isRegistered<RoomController>() |
|
|
|
? Get.find<RoomController>() |
|
|
|
: null; |
|
|
|
|
|
|
|
// 获取 rtcChannelDetail |
|
|
|
final rtcChannelDetail = roomController?.rtcChannelDetail.value; |
|
|
|
|
|
|
|
// 获取当前用户ID |
|
|
|
final currentUserId = GlobalData().userId ?? GlobalData().userData?.id; |
|
|
|
|
|
|
|
// 构建用户列表(anchorInfo, maleInfo, femaleInfo) |
|
|
|
final List<RtcSeatUserInfo> userList = []; |
|
|
|
if (rtcChannelDetail?.anchorInfo != null) { |
|
|
|
userList.add(rtcChannelDetail!.anchorInfo!); |
|
|
|
} |
|
|
|
if (rtcChannelDetail?.maleInfo != null) { |
|
|
|
userList.add(rtcChannelDetail!.maleInfo!); |
|
|
|
} |
|
|
|
if (rtcChannelDetail?.femaleInfo != null) { |
|
|
|
userList.add(rtcChannelDetail!.femaleInfo!); |
|
|
|
} |
|
|
|
|
|
|
|
// 移除本人 |
|
|
|
final filteredUserList = userList.where((user) { |
|
|
|
// 通过 userId 或 miId 判断是否是本人 |
|
|
|
return user.userId != currentUserId && user.miId != currentUserId; |
|
|
|
}).toList(); |
|
|
|
|
|
|
|
// 最多显示3个用户 |
|
|
|
final displayUsers = filteredUserList.take(3).toList(); |
|
|
|
|
|
|
|
return Container( |
|
|
|
height: 53.w, |
|
|
|
padding: EdgeInsets.symmetric(horizontal: 10.w), |
|
|
|
child: Row( |
|
|
|
children: [ |
|
|
|
Row( |
|
|
|
children: [ |
|
|
|
Text( |
|
|
|
"送给: ", |
|
|
|
style: TextStyle(fontSize: 13.w, color: Colors.white), |
|
|
|
), |
|
|
|
SizedBox(width: 6.w), |
|
|
|
...displayUsers.asMap().entries.map((entry) { |
|
|
|
final index = entry.key; |
|
|
|
final user = entry.value; |
|
|
|
final isSelected = _selectedUserIds.contains(user.userId); |
|
|
|
return GestureDetector( |
|
|
|
onTap: () => _toggleUserSelection(user.userId), |
|
|
|
child: Padding( |
|
|
|
padding: EdgeInsets.only(right: 10.w), |
|
|
|
child: Stack( |
|
|
|
children: [ |
|
|
|
ClipRRect( |
|
|
|
borderRadius: BorderRadius.all( |
|
|
|
Radius.circular(index == 0 ? 68.w : 34.w), |
|
|
|
), |
|
|
|
border: Border.all( |
|
|
|
width: index == 0 ? 2 : 1, |
|
|
|
color: const Color.fromRGBO(117, 98, 249, 1), |
|
|
|
child: Container( |
|
|
|
width: 34.w, |
|
|
|
height: 34.w, |
|
|
|
decoration: BoxDecoration( |
|
|
|
borderRadius: BorderRadius.all( |
|
|
|
Radius.circular(index == 0 ? 68.w : 34.w), |
|
|
|
), |
|
|
|
border: Border.all( |
|
|
|
width: index == 0 ? 2 : 1, |
|
|
|
color: const Color.fromRGBO(117, 98, 249, 1), |
|
|
|
), |
|
|
|
), |
|
|
|
child: user.profilePhoto.isNotEmpty |
|
|
|
? CachedNetworkImage( |
|
|
|
imageUrl: user.profilePhoto, |
|
|
|
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: 32.w, |
|
|
|
height: 32.w, |
|
|
|
), |
|
|
|
) |
|
|
|
: Image.asset( |
|
|
|
Assets.imagesUserAvatar, |
|
|
|
width: 32.w, |
|
|
|
height: 32.w, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
child: Image.asset( |
|
|
|
Assets.imagesUserAvatar, |
|
|
|
width: 32.w, |
|
|
|
height: 32.w, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
Positioned( |
|
|
|
bottom: 0, |
|
|
|
right: 2.w, |
|
|
|
child: Container( |
|
|
|
width: 12.w, |
|
|
|
height: 12.w, |
|
|
|
decoration: BoxDecoration( |
|
|
|
borderRadius: BorderRadius.all(Radius.circular(12.w)), |
|
|
|
color: const Color.fromRGBO(117, 98, 249, 1), |
|
|
|
), |
|
|
|
child: Center( |
|
|
|
child: Image.asset( |
|
|
|
Assets.imagesCheck, |
|
|
|
width: 6.w, |
|
|
|
height: 4.w, |
|
|
|
if (isSelected) |
|
|
|
Positioned( |
|
|
|
bottom: 0, |
|
|
|
right: 2.w, |
|
|
|
child: Container( |
|
|
|
width: 12.w, |
|
|
|
height: 12.w, |
|
|
|
decoration: BoxDecoration( |
|
|
|
borderRadius: BorderRadius.all( |
|
|
|
Radius.circular(12.w), |
|
|
|
), |
|
|
|
color: const Color.fromRGBO(117, 98, 249, 1), |
|
|
|
), |
|
|
|
child: Center( |
|
|
|
child: Image.asset( |
|
|
|
Assets.imagesCheck, |
|
|
|
width: 6.w, |
|
|
|
height: 4.w, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
}), |
|
|
|
], |
|
|
|
), |
|
|
|
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( |
|
|
|
"全选", |
|
|
|
style: TextStyle(fontSize: 13.w, color: Colors.white), |
|
|
|
// 如果列表为空,不显示全选按钮 |
|
|
|
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), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
Widget _buildTab() { |
|
|
|
@ -163,34 +283,61 @@ class LiveGiftPopup extends StatelessWidget { |
|
|
|
} |
|
|
|
|
|
|
|
Widget _buildGiftSwiper() { |
|
|
|
if (widget.giftList.isEmpty) { |
|
|
|
return Expanded( |
|
|
|
child: Center( |
|
|
|
child: Text( |
|
|
|
'暂无礼物', |
|
|
|
style: TextStyle(fontSize: 14.w, color: Colors.white70), |
|
|
|
), |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// 计算需要多少页,每页显示8个礼物(2行4列) |
|
|
|
final itemsPerPage = 8; |
|
|
|
final totalPages = (widget.giftList.length / itemsPerPage).ceil(); |
|
|
|
|
|
|
|
return Expanded( |
|
|
|
child: ValueListenableBuilder<int?>( |
|
|
|
valueListenable: activeGift, |
|
|
|
valueListenable: widget.activeGift, |
|
|
|
builder: (context, active, _) { |
|
|
|
return Swiper( |
|
|
|
autoplay: false, |
|
|
|
itemCount: 6, |
|
|
|
loop: true, |
|
|
|
pagination: const SwiperPagination( |
|
|
|
alignment: Alignment.bottomCenter, |
|
|
|
builder: TDSwiperDotsPagination( |
|
|
|
color: Color.fromRGBO(144, 144, 144, 1), |
|
|
|
activeColor: Color.fromRGBO(77, 77, 77, 1), |
|
|
|
), |
|
|
|
), |
|
|
|
itemBuilder: (context, index) { |
|
|
|
itemCount: totalPages, |
|
|
|
loop: false, |
|
|
|
pagination: totalPages > 1 |
|
|
|
? const SwiperPagination( |
|
|
|
alignment: Alignment.bottomCenter, |
|
|
|
builder: TDSwiperDotsPagination( |
|
|
|
color: Color.fromRGBO(144, 144, 144, 1), |
|
|
|
activeColor: Color.fromRGBO(77, 77, 77, 1), |
|
|
|
), |
|
|
|
) |
|
|
|
: null, |
|
|
|
itemBuilder: (context, pageIndex) { |
|
|
|
final startIndex = pageIndex * itemsPerPage; |
|
|
|
final endIndex = (startIndex + itemsPerPage).clamp( |
|
|
|
0, |
|
|
|
widget.giftList.length, |
|
|
|
); |
|
|
|
final pageItems = widget.giftList.sublist(startIndex, endIndex); |
|
|
|
|
|
|
|
return Align( |
|
|
|
alignment: Alignment.topCenter, |
|
|
|
child: Wrap( |
|
|
|
spacing: 7.w, |
|
|
|
runSpacing: 7.w, |
|
|
|
children: [ |
|
|
|
...giftList.asMap().entries.map( |
|
|
|
(entry) => LiveRoomGiftItem( |
|
|
|
...pageItems.asMap().entries.map((entry) { |
|
|
|
final globalIndex = startIndex + entry.key; |
|
|
|
return LiveRoomGiftItem( |
|
|
|
item: entry.value, |
|
|
|
active: active ?? 0, |
|
|
|
index: entry.key, |
|
|
|
changeActive: changeActive, |
|
|
|
), |
|
|
|
), |
|
|
|
index: globalIndex, |
|
|
|
changeActive: widget.changeActive, |
|
|
|
); |
|
|
|
}), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
@ -209,26 +356,18 @@ class LiveGiftPopup extends StatelessWidget { |
|
|
|
children: [ |
|
|
|
Row( |
|
|
|
children: [ |
|
|
|
Image.asset( |
|
|
|
Assets.imagesRoseGift, |
|
|
|
width: 21.w, |
|
|
|
height: 21.w, |
|
|
|
), |
|
|
|
Image.asset(Assets.imagesRoseGift, width: 21.w, height: 21.w), |
|
|
|
SizedBox(width: 8.w), |
|
|
|
Text( |
|
|
|
"9", |
|
|
|
style: TextStyle(fontSize: 13.w, color: Colors.white), |
|
|
|
), |
|
|
|
SizedBox(width: 12.w), |
|
|
|
Image.asset( |
|
|
|
Assets.imagesRoseGift, |
|
|
|
width: 68.w, |
|
|
|
height: 33.w, |
|
|
|
), |
|
|
|
Image.asset(Assets.imagesRoseGift, width: 68.w, height: 33.w), |
|
|
|
], |
|
|
|
), |
|
|
|
ValueListenableBuilder<int>( |
|
|
|
valueListenable: giftNum, |
|
|
|
valueListenable: widget.giftNum, |
|
|
|
builder: (context, num, _) { |
|
|
|
return Row( |
|
|
|
children: [ |
|
|
|
@ -236,8 +375,8 @@ class LiveGiftPopup extends StatelessWidget { |
|
|
|
label: "-", |
|
|
|
enabled: num > 1, |
|
|
|
onTap: () { |
|
|
|
if (giftNum.value <= 1) return; |
|
|
|
giftNum.value -= 1; |
|
|
|
if (widget.giftNum.value <= 1) return; |
|
|
|
widget.giftNum.value -= 1; |
|
|
|
}, |
|
|
|
), |
|
|
|
SizedBox( |
|
|
|
@ -253,7 +392,7 @@ class LiveGiftPopup extends StatelessWidget { |
|
|
|
label: "+", |
|
|
|
enabled: true, |
|
|
|
onTap: () { |
|
|
|
giftNum.value += 1; |
|
|
|
widget.giftNum.value += 1; |
|
|
|
}, |
|
|
|
), |
|
|
|
SizedBox(width: 9.w), |
|
|
|
@ -323,4 +462,3 @@ class LiveGiftPopup extends StatelessWidget { |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|