chatapp3-flutter/lib/modules/home/popular/party/sc_home_party_page.dart
2026-04-15 11:44:49 +08:00

840 lines
30 KiB
Dart

import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
import 'package:provider/provider.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../../../../app_localizations.dart';
import '../../../../app/constants/sc_global_config.dart';
import '../../../../app/routes/sc_fluro_navigator.dart';
import '../../../../shared/tools/sc_banner_utils.dart';
import '../../../../shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
import '../../../../shared/business_logic/models/res/follow_room_res.dart';
import '../../../../shared/business_logic/models/res/room_res.dart';
import '../../../../services/audio/rtc_manager.dart';
import '../../../../services/general/sc_app_general_manager.dart';
import '../../../../ui_kit/components/sc_compontent.dart';
import '../../../../ui_kit/components/text/sc_text.dart';
import '../../../../ui_kit/widgets/room/room_live_audio_indicator.dart';
import '../../../index/main_route.dart';
const Duration _kPartySkeletonAnimationDuration = Duration(milliseconds: 1450);
const Color _kPartySkeletonShell = Color(0xFF0F3730);
const Color _kPartySkeletonShellStrong = Color(0xFF1A4A41);
const Color _kPartySkeletonBoneBase = Color(0xFF2B5C53);
const Color _kPartySkeletonBoneHighlight = Color(0xFF5F8177);
const Color _kPartySkeletonBorder = Color(0x66D8B57B);
class SCHomePartyPage extends StatefulWidget {
const SCHomePartyPage({super.key});
@override
State<SCHomePartyPage> createState() => _HomePartyPageState();
}
class _HomePartyPageState extends State<SCHomePartyPage>
with SingleTickerProviderStateMixin {
List<FollowRoomRes> historyRooms = [];
String? lastId;
bool isLoading = false;
bool _isLeaderboardLoading = false;
final RefreshController _refreshController = RefreshController(
initialRefresh: false,
);
late final AnimationController _skeletonController;
List<SocialChatRoomRes> rooms = [];
int _currentIndex = 0;
@override
void initState() {
super.initState();
_skeletonController = AnimationController(
vsync: this,
duration: _kPartySkeletonAnimationDuration,
)..repeat();
loadData();
}
@override
void dispose() {
_skeletonController.dispose();
_refreshController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.transparent,
body: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: [
SmartRefresher(
enablePullDown: true,
enablePullUp: false,
controller: _refreshController,
onRefresh: () {
loadData();
},
onLoading: () {},
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Stack(
alignment: AlignmentDirectional.center,
children: [
Consumer<SCAppGeneralManager>(
builder: (context, ref, child) {
return _banner(ref);
},
),
],
),
Consumer<SCAppGeneralManager>(
builder: (context, ref, child) {
if (_shouldShowLeaderboardSkeleton(ref)) {
return _buildLeaderboardSkeleton();
}
return _banner2(ref);
},
),
_buildRoomsSection(),
],
),
),
),
],
),
);
}
_banner(SCAppGeneralManager ref) {
if (ref.exploreBanners.isEmpty) {
return Container();
}
return Stack(
alignment: AlignmentDirectional.bottomCenter,
children: [
CarouselSlider(
options: CarouselOptions(
height: 95.w,
autoPlay: true,
// 启用自动播放
enlargeCenterPage: false,
// 居中放大当前页面
enableInfiniteScroll: true,
// 启用无限循环
autoPlayAnimationDuration: Duration(milliseconds: 800),
// 自动播放动画时长
viewportFraction: 1,
// 视口分数
onPageChanged: (index, reason) {
_currentIndex = index;
setState(() {});
},
),
items:
ref.exploreBanners.map((item) {
return GestureDetector(
child: netImage(
url: item.cover ?? "",
height: 95.w,
width: ScreenUtil().screenWidth * 0.9,
fit: BoxFit.fill,
borderRadius: BorderRadius.circular(12.w),
),
onTap: () {
SCBannerUtils.openBanner(item, context);
},
);
}).toList(),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children:
ref.exploreBanners.asMap().entries.map((entry) {
return Container(
width: 6.0,
height: 6.0,
margin: EdgeInsets.symmetric(vertical: 8.0, horizontal: 4.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
color:
_currentIndex == entry.key ? Colors.blue : Colors.white,
),
);
}).toList(),
),
],
);
}
_banner2(SCAppGeneralManager ref) {
String roomGiftOneAvatar = "";
String roomGiftTwoAvatar = "";
String roomGiftThreeAvatar = "";
if (ref.appLeaderResult?.roomGiftsLeaderboard != null &&
ref.appLeaderResult?.roomGiftsLeaderboard?.weekly != null) {
if ((ref.appLeaderResult?.roomGiftsLeaderboard?.weekly?.isNotEmpty ??
false) &&
ref.appLeaderResult?.roomGiftsLeaderboard?.weekly?.first != null) {
roomGiftOneAvatar =
ref.appLeaderResult?.roomGiftsLeaderboard?.weekly?.first.avatar ??
"";
}
if (ref.appLeaderResult!.roomGiftsLeaderboard!.weekly!.length > 1 &&
ref.appLeaderResult?.roomGiftsLeaderboard?.weekly?[1] != null) {
roomGiftTwoAvatar =
ref.appLeaderResult?.roomGiftsLeaderboard?.weekly![1].avatar ?? "";
}
if (ref.appLeaderResult!.roomGiftsLeaderboard!.weekly!.length > 2 &&
ref.appLeaderResult?.roomGiftsLeaderboard?.weekly?[2] != null) {
roomGiftThreeAvatar =
ref.appLeaderResult?.roomGiftsLeaderboard?.weekly![2].avatar ?? "";
}
}
String wealthOneAvatar = "";
String wealthTwoAvatar = "";
String wealthThreeAvatar = "";
if (ref.appLeaderResult?.giftsSendLeaderboard != null &&
ref.appLeaderResult?.giftsSendLeaderboard?.weekly != null) {
if ((ref.appLeaderResult?.giftsSendLeaderboard?.weekly?.isNotEmpty ??
false) &&
ref.appLeaderResult?.giftsSendLeaderboard?.weekly?.first != null) {
wealthOneAvatar =
ref.appLeaderResult?.giftsSendLeaderboard?.weekly?.first.avatar ??
"";
}
if (ref.appLeaderResult!.giftsSendLeaderboard!.weekly!.length > 1 &&
ref.appLeaderResult?.giftsSendLeaderboard?.weekly?[1] != null) {
wealthTwoAvatar =
ref.appLeaderResult?.giftsSendLeaderboard?.weekly![1].avatar ?? "";
}
if (ref.appLeaderResult!.giftsSendLeaderboard!.weekly!.length > 2 &&
ref.appLeaderResult?.giftsSendLeaderboard?.weekly?[2] != null) {
wealthThreeAvatar =
ref.appLeaderResult?.giftsSendLeaderboard?.weekly![2].avatar ?? "";
}
}
String charmOneAvatar = "";
String charmTwoAvatar = "";
String charmThreeAvatar = "";
if (ref.appLeaderResult?.giftsReceivedLeaderboard != null &&
ref.appLeaderResult?.giftsReceivedLeaderboard?.weekly != null) {
if ((ref.appLeaderResult?.giftsReceivedLeaderboard?.weekly?.isNotEmpty ??
false) &&
ref.appLeaderResult?.giftsReceivedLeaderboard?.weekly?.first !=
null) {
charmOneAvatar =
ref
.appLeaderResult
?.giftsReceivedLeaderboard
?.weekly
?.first
.avatar ??
"";
}
if (ref.appLeaderResult!.giftsReceivedLeaderboard!.weekly!.length > 1 &&
ref.appLeaderResult?.giftsReceivedLeaderboard?.weekly?[1] != null) {
charmTwoAvatar =
ref.appLeaderResult?.giftsReceivedLeaderboard?.weekly![1].avatar ??
"";
}
if (ref.appLeaderResult!.giftsReceivedLeaderboard!.weekly!.length > 2 &&
ref.appLeaderResult?.giftsReceivedLeaderboard?.weekly?[2] != null) {
charmThreeAvatar =
ref.appLeaderResult?.giftsReceivedLeaderboard?.weekly![2].avatar ??
"";
}
}
if (roomGiftOneAvatar.isEmpty &&
wealthOneAvatar.isEmpty &&
charmOneAvatar.isEmpty) {
return Container();
}
return SizedBox(
height: 130.w,
child: Row(
children: [
Expanded(
child: GestureDetector(
child: Stack(
alignment: AlignmentDirectional.center,
children: [
PositionedDirectional(
start: 26.w,
top: 65.w,
child: head(url: wealthTwoAvatar, width: 30.w),
),
Positioned(
top: 35.w,
child: head(url: wealthOneAvatar, width: 36.w),
),
PositionedDirectional(
end: 24.w,
top: 65.w,
child: head(url: wealthThreeAvatar, width: 30.w),
),
Image.asset(
SCGlobalConfig.businessLogicStrategy
.getPopularLeaderboardBackgroundImage('wealth'),
fit: BoxFit.fill,
),
],
),
onTap: () {
SCNavigatorUtils.push(
context,
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(SCGlobalConfig.wealthRankUrl)}&showTitle=false",
replace: false,
);
},
),
),
Expanded(
child: GestureDetector(
child: Stack(
alignment: AlignmentDirectional.center,
children: [
PositionedDirectional(
start: 24.w,
top: 68.w,
child: head(url: roomGiftTwoAvatar, width: 30.w),
),
Positioned(
top: 35.w,
child: head(url: roomGiftOneAvatar, width: 38.w),
),
PositionedDirectional(
end: 25.w,
top: 69.w,
child: head(url: roomGiftThreeAvatar, width: 30.w),
),
Image.asset(
SCGlobalConfig.businessLogicStrategy
.getPopularLeaderboardBackgroundImage('room'),
fit: BoxFit.fill,
),
],
),
onTap: () {
SCNavigatorUtils.push(
context,
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(SCGlobalConfig.roomRankUrl)}&showTitle=false",
replace: false,
);
},
),
),
Expanded(
child: GestureDetector(
child: Stack(
alignment: AlignmentDirectional.center,
children: [
PositionedDirectional(
start: 26.w,
top: 66.w,
child: head(url: charmTwoAvatar, width: 30.w),
),
Positioned(
top: 38.w,
child: head(url: charmOneAvatar, width: 37.w),
),
PositionedDirectional(
end: 25.w,
top: 66.w,
child: head(url: charmThreeAvatar, width: 30.w),
),
Image.asset(
SCGlobalConfig.businessLogicStrategy
.getPopularLeaderboardBackgroundImage('charm'),
fit: BoxFit.fill,
),
],
),
onTap: () {
SCNavigatorUtils.push(
context,
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(SCGlobalConfig.charmRankUrl)}&showTitle=false",
replace: false,
);
},
),
),
],
),
);
}
Widget _buildRoomsSection() {
if (isLoading && rooms.isEmpty) {
return _buildRoomGridSkeleton();
}
if (rooms.isEmpty) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
loadData();
},
child: mainEmpty(msg: SCAppLocalizations.of(context)!.noData),
);
}
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
padding: EdgeInsets.all(10),
itemCount: rooms.length,
itemBuilder: (context, index) {
return _buildItem(rooms[index], index);
},
);
}
bool _shouldShowLeaderboardSkeleton(SCAppGeneralManager ref) {
return _isLeaderboardLoading && !_hasLeaderboardData(ref);
}
bool _hasLeaderboardData(SCAppGeneralManager ref) {
final appLeaderResult = ref.appLeaderResult;
return (appLeaderResult?.giftsSendLeaderboard?.weekly?.isNotEmpty ??
false) ||
(appLeaderResult?.roomGiftsLeaderboard?.weekly?.isNotEmpty ?? false) ||
(appLeaderResult?.giftsReceivedLeaderboard?.weekly?.isNotEmpty ??
false);
}
Widget _buildLeaderboardSkeleton() {
return SizedBox(
height: 130.w,
child: Row(
children: List.generate(3, (index) {
final bool isCenterCard = index == 1;
return Expanded(
child: Padding(
padding: EdgeInsets.fromLTRB(
4.w,
isCenterCard ? 0 : 6.w,
4.w,
isCenterCard ? 0 : 6.w,
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18.w),
border: Border.all(color: _kPartySkeletonBorder, width: 1.w),
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [_kPartySkeletonShellStrong, _kPartySkeletonShell],
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.18),
blurRadius: 14.w,
offset: Offset(0, 8.w),
),
],
),
child: Stack(
alignment: Alignment.center,
children: [
Positioned.fill(
child: Padding(
padding: EdgeInsets.all(1.w),
child: ClipRRect(
borderRadius: BorderRadius.circular(17.w),
child: Opacity(
opacity: 0.34,
child: _buildSkeletonPanel(),
),
),
),
),
Positioned(
top: 21.w,
child: _buildSkeletonBone(
width: isCenterCard ? 42.w : 38.w,
height: isCenterCard ? 42.w : 38.w,
shape: BoxShape.circle,
),
),
PositionedDirectional(
start: 16.w,
top: 56.w,
child: _buildSkeletonBone(
width: 28.w,
height: 28.w,
shape: BoxShape.circle,
),
),
PositionedDirectional(
end: 16.w,
top: 56.w,
child: _buildSkeletonBone(
width: 28.w,
height: 28.w,
shape: BoxShape.circle,
),
),
Positioned(
bottom: 16.w,
child: _buildSkeletonBone(
width: isCenterCard ? 56.w : 48.w,
height: 10.w,
borderRadius: BorderRadius.circular(999.w),
),
),
],
),
),
),
);
}),
),
);
}
Widget _buildRoomGridSkeleton() {
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
padding: EdgeInsets.all(10.w),
itemCount: 6,
itemBuilder: (context, index) => _buildRoomSkeletonCard(),
);
}
Widget _buildRoomSkeletonCard() {
return Container(
margin: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14.w),
border: Border.all(color: _kPartySkeletonBorder, width: 1.w),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.16),
blurRadius: 16.w,
offset: Offset(0, 8.w),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(14.w),
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Positioned.fill(
child: DecoratedBox(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [_kPartySkeletonShellStrong, _kPartySkeletonShell],
),
),
),
),
Positioned.fill(
child: Padding(
padding: EdgeInsets.only(bottom: 34.w),
child: _buildSkeletonPanel(
borderRadius: BorderRadius.vertical(
top: Radius.circular(14.w),
),
),
),
),
Positioned(
top: 12.w,
right: 12.w,
child: _buildSkeletonBone(
width: 30.w,
height: 12.w,
borderRadius: BorderRadius.circular(999.w),
),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Container(
height: 34.w,
padding: EdgeInsets.symmetric(horizontal: 10.w),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.24),
border: Border(
top: BorderSide(
color: Colors.white.withValues(alpha: 0.05),
width: 1.w,
),
),
),
child: Row(
children: [
_buildSkeletonBone(
width: 20.w,
height: 13.w,
borderRadius: BorderRadius.circular(3.w),
),
SizedBox(width: 6.w),
Expanded(
child: _buildSkeletonBone(
height: 10.w,
borderRadius: BorderRadius.circular(999.w),
),
),
SizedBox(width: 8.w),
_buildSkeletonBone(
width: 18.w,
height: 10.w,
borderRadius: BorderRadius.circular(999.w),
),
],
),
),
),
],
),
),
);
}
Widget _buildSkeletonPanel({
BorderRadius? borderRadius,
BoxShape shape = BoxShape.rectangle,
}) {
return AnimatedBuilder(
animation: _skeletonController,
builder: (context, child) {
final double slideOffset = (_skeletonController.value * 2) - 1;
return DecoratedBox(
decoration: BoxDecoration(
shape: shape,
borderRadius:
shape == BoxShape.circle
? null
: (borderRadius ?? BorderRadius.circular(12.w)),
gradient: LinearGradient(
begin: Alignment(-1.4 + slideOffset, -0.2),
end: Alignment(1.4 + slideOffset, 0.2),
colors: const [
_kPartySkeletonBoneBase,
_kPartySkeletonBoneBase,
_kPartySkeletonBoneHighlight,
_kPartySkeletonBoneBase,
],
stops: const [0.0, 0.36, 0.54, 1.0],
),
),
);
},
);
}
Widget _buildSkeletonBone({
double? width,
double? height,
BorderRadius? borderRadius,
BoxShape shape = BoxShape.rectangle,
}) {
return SizedBox(
width: width,
height: height,
child: _buildSkeletonPanel(borderRadius: borderRadius, shape: shape),
);
}
loadData() {
final generalManager = Provider.of<SCAppGeneralManager>(
context,
listen: false,
);
setState(() {
isLoading = true;
_isLeaderboardLoading = generalManager.appLeaderResult == null;
});
SCChatRoomRepository()
.discovery(allRegion: true)
.then((values) {
rooms = values;
isLoading = false;
_refreshController.refreshCompleted();
_refreshController.loadComplete();
if (mounted) setState(() {});
})
.catchError((e) {
_refreshController.loadNoData();
_refreshController.refreshCompleted();
isLoading = false;
if (mounted) setState(() {});
});
generalManager.loadMainBanner();
generalManager.appLeaderboard().whenComplete(() {
if (!mounted) {
return;
}
setState(() {
_isLeaderboardLoading = false;
});
});
}
_buildItem(SocialChatRoomRes res, int index) {
return SCDebounceWidget(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.w),
color: Colors.transparent,
),
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Container(
padding: EdgeInsets.all(3.w),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("sc_images/index/sc_icon_room_bord.png"),
fit: BoxFit.fill,
),
),
child: netImage(
url: resolveRoomCoverUrl(res.id, res.roomCover),
defaultImg: kRoomCoverDefaultImg,
borderRadius: BorderRadius.circular(12.w),
width: 200.w,
height: 200.w,
),
),
Container(
padding: EdgeInsets.symmetric(vertical: 6.w),
margin: EdgeInsets.symmetric(horizontal: 1.w),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
"sc_images/index/sc_icon_index_room_brd.png",
),
fit: BoxFit.fill,
),
),
child: Row(
children: [
SizedBox(width: 10.w),
Consumer<SCAppGeneralManager>(
builder: (_, provider, __) {
return netImage(
url:
"${provider.findCountryByName(res.countryName ?? "")?.nationalFlag}",
width: 20.w,
height: 13.w,
borderRadius: BorderRadius.circular(2.w),
);
},
),
SizedBox(width: 5.w),
Expanded(
child: SizedBox(
height: 17.w,
child: Align(
alignment: Alignment.centerLeft,
child: Transform.translate(
offset: Offset(0, -0.6.w),
child: text(
res.roomName ?? "",
fontSize: 13.sp,
textColor: Color(0xffffffff),
fontWeight: FontWeight.w400,
lineHeight: 1,
),
),
),
// (roomRes.roomProfile?.roomName?.length ?? 0) > 10
// ? Marquee(
// text: roomRes.roomProfile?.roomName ?? "",
// style: TextStyle(
// fontSize: 15.sp,
// color: Color(0xffffffff),
// fontWeight: FontWeight.w400,
// decoration: TextDecoration.none,
// ),
// scrollAxis: Axis.horizontal,
// crossAxisAlignment: CrossAxisAlignment.start,
// blankSpace: 20.0,
// velocity: 40.0,
// pauseAfterRound: Duration(seconds: 1),
// accelerationDuration: Duration(seconds: 1),
// accelerationCurve: Curves.easeOut,
// decelerationDuration: Duration(
// milliseconds: 500,
// ),
// decelerationCurve: Curves.easeOut,
// )
// : Text(
// roomRes.roomProfile?.roomName ?? "",
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// style: TextStyle(
// fontSize: 15.sp,
// color: Color(0xffffffff),
// fontWeight: FontWeight.w400,
// decoration: TextDecoration.none,
// ),
// ),
),
),
SizedBox(width: 5.w),
(res.extValues?.roomSetting?.password?.isEmpty ?? false)
? SCRoomLiveAudioIndicator(width: 14.w, height: 14.w)
: Image.asset(
"sc_images/index/sc_icon_room_suo.png",
width: 20.w,
height: 20.w,
),
(res.extValues?.roomSetting?.password?.isEmpty ?? false)
? SizedBox(width: 3.w)
: Container(height: 10.w),
(res.extValues?.roomSetting?.password?.isEmpty ?? false)
? text(
res.extValues?.memberQuantity ?? "0",
fontSize: 10.sp,
lineHeight: 1,
)
: Container(height: 10.w),
SizedBox(width: 10.w),
],
),
),
],
),
),
onTap: () {
Provider.of<RtcProvider>(
context,
listen: false,
).joinVoiceRoomSession(context, res.id ?? "");
},
);
}
}