骨架屏以及一些已知bug

This commit is contained in:
NIGGER SLAYER 2026-04-15 11:44:49 +08:00
parent 37a04d5ad7
commit db3edcefb4
25 changed files with 4552 additions and 3419 deletions

View File

@ -15,6 +15,11 @@ import 'package:yumi/app/constants/sc_global_config.dart';
import '../../shared/data_sources/models/enum/sc_gift_type.dart';
enum _GiftGridPageStatus { idle, loading, ready }
const Duration _kGiftPageSkeletonMinDuration = Duration(milliseconds: 420);
const Duration _kGiftPageSkeletonMaxDuration = Duration(milliseconds: 900);
class GiftTabPage extends StatefulWidget {
String type;
bool isDark = true;
@ -26,13 +31,19 @@ class GiftTabPage extends StatefulWidget {
_GiftTabPageState createState() => _GiftTabPageState();
}
class _GiftTabPageState extends State<GiftTabPage> {
PageController _giftPageController = PageController();
class _GiftTabPageState extends State<GiftTabPage>
with AutomaticKeepAliveClientMixin {
final PageController _giftPageController = PageController();
final Map<int, _GiftGridPageStatus> _pageStatuses =
<int, _GiftGridPageStatus>{};
int _index = 0;
int checkedIndex = 0;
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
@ -47,13 +58,20 @@ class _GiftTabPageState extends State<GiftTabPage> {
@override
Widget build(BuildContext context) {
super.build(context);
return Consumer<SCAppGeneralManager>(
builder: (context, ref, child) {
return (ref.giftByTab[widget.type] ?? []).isEmpty
? mainEmpty(
final gifts = ref.giftByTab[widget.type] ?? <SocialChatGiftRes>[];
if (gifts.isNotEmpty) {
_schedulePageWarmup(ref, gifts, _index);
}
return gifts.isEmpty
? (ref.isGiftListLoading
? _buildGiftPageSkeleton()
: mainEmpty(
textColor: Colors.white54,
msg: SCAppLocalizations.of(context)!.noData,
)
))
: Column(
children: [
SizedBox(height: 23.w),
@ -64,16 +82,21 @@ class _GiftTabPageState extends State<GiftTabPage> {
setState(() {
_index = i;
});
_schedulePageWarmup(ref, gifts, i);
},
itemBuilder: (c, i) {
var current =
(ref.giftByTab[widget.type]!.length - (i * 8));
_schedulePageWarmup(ref, gifts, i);
var current = (gifts.length - (i * 8));
int size =
current > 8
? 8
: current % 8 == 0
? 8
: current % 8;
if ((_pageStatuses[i] ?? _GiftGridPageStatus.idle) !=
_GiftGridPageStatus.ready) {
return _buildGiftPageSkeleton();
}
return GridView.builder(
shrinkWrap: true,
padding: EdgeInsets.symmetric(horizontal: 12.w),
@ -86,14 +109,11 @@ class _GiftTabPageState extends State<GiftTabPage> {
),
itemCount: size,
itemBuilder: (BuildContext context, int index) {
return _bagItem(
ref.giftByTab[widget.type]![(i * 8) + index],
ref,
);
return _bagItem(gifts[(i * 8) + index], ref);
},
);
},
itemCount: (ref.giftByTab[widget.type]!.length / 8).ceil(),
itemCount: (gifts.length / 8).ceil(),
),
),
_indicator(ref),
@ -104,6 +124,173 @@ class _GiftTabPageState extends State<GiftTabPage> {
);
}
void _schedulePageWarmup(
SCAppGeneralManager ref,
List<SocialChatGiftRes> gifts,
int pageIndex,
) {
final totalPages = (gifts.length / 8).ceil();
_ensurePageReady(ref, totalPages, pageIndex);
_ensurePageReady(ref, totalPages, pageIndex + 1);
}
void _ensurePageReady(
SCAppGeneralManager ref,
int totalPages,
int pageIndex,
) {
if (pageIndex < 0 || pageIndex >= totalPages) {
return;
}
final status = _pageStatuses[pageIndex] ?? _GiftGridPageStatus.idle;
if (status == _GiftGridPageStatus.loading ||
status == _GiftGridPageStatus.ready) {
return;
}
_pageStatuses[pageIndex] = _GiftGridPageStatus.loading;
WidgetsBinding.instance.addPostFrameCallback((_) async {
final warmupTask = ref.warmGiftPageCovers(
widget.type,
pageIndex: pageIndex,
);
await Future.any<void>([
Future.wait<void>([
warmupTask,
Future<void>.delayed(_kGiftPageSkeletonMinDuration),
]),
Future<void>.delayed(_kGiftPageSkeletonMaxDuration),
]);
if (!mounted) {
return;
}
setState(() {
_pageStatuses[pageIndex] = _GiftGridPageStatus.ready;
});
});
}
Widget _buildGiftPageSkeleton() {
return Stack(
children: [
Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.w),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.white.withValues(alpha: 0.1),
Colors.white.withValues(alpha: 0.04),
],
),
),
),
),
Positioned.fill(
child: Center(
child: Container(
width: 132.w,
height: 132.w,
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(20.w),
border: Border.all(
color: Colors.white.withValues(alpha: 0.12),
width: 1.w,
),
),
alignment: Alignment.center,
child: Opacity(
opacity: 0.75,
child: Image.asset(
"sc_images/general/sc_icon_loading.png",
width: 92.w,
fit: BoxFit.contain,
),
),
),
),
),
GridView.builder(
shrinkWrap: true,
padding: EdgeInsets.symmetric(horizontal: 12.w),
physics: NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
childAspectRatio: 0.75,
mainAxisSpacing: 8.w,
crossAxisSpacing: 8.w,
),
itemCount: 8,
itemBuilder: (context, index) => _buildGiftSkeletonCard(),
),
],
);
}
Widget _buildGiftSkeletonCard() {
final baseColor = Colors.white.withValues(alpha: 0.12);
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.w),
color: Colors.white.withValues(alpha: 0.08),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 48.w,
height: 48.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.w),
color: baseColor,
image: const DecorationImage(
image: AssetImage("sc_images/general/sc_icon_loading.png"),
fit: BoxFit.cover,
opacity: 0.32,
),
),
),
SizedBox(height: 7.w),
Container(
width: 52.w,
height: 8.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(999.w),
color: baseColor,
),
),
SizedBox(height: 6.w),
Container(
width: 40.w,
height: 8.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(999.w),
color: baseColor,
),
),
],
),
);
}
Widget _buildGiftCoverLoading() {
return Container(
width: 48.w,
height: 48.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.w),
color: Colors.white.withValues(alpha: 0.08),
),
clipBehavior: Clip.antiAlias,
child: Image.asset(
"sc_images/general/sc_icon_loading.png",
fit: BoxFit.cover,
),
);
}
Widget _bagItem(SocialChatGiftRes gift, SCAppGeneralManager ref) {
return gift.id == "-1000"
? GestureDetector(
@ -236,6 +423,8 @@ class _GiftTabPageState extends State<GiftTabPage> {
fit: BoxFit.cover,
width: 48.w,
height: 48.w,
loadingWidget: _buildGiftCoverLoading(),
errorWidget: _buildGiftCoverLoading(),
),
SizedBox(height: 5.w),
Container(

View File

@ -1,4 +1,3 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:yumi/app_localizations.dart';
@ -6,22 +5,25 @@ import 'package:provider/provider.dart';
import 'package:yumi/ui_kit/components/sc_compontent.dart';
import 'package:yumi/ui_kit/components/sc_page_list.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart';
import 'package:yumi/ui_kit/widgets/room/room_live_audio_indicator.dart';
import 'package:yumi/app/constants/sc_screen.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
import 'package:yumi/shared/business_logic/models/res/follow_room_res.dart';
import 'package:yumi/services/general/sc_app_general_manager.dart';
import 'package:yumi/services/audio/rtc_manager.dart';
import '../mine/sc_home_mine_skeleton.dart';
///
class SCRoomFollowPage extends SCPageList {
const SCRoomFollowPage({super.key});
@override
_RoomFollowPageState createState() => _RoomFollowPageState();
SCPageListState createState() => _RoomFollowPageState();
}
class _RoomFollowPageState
extends SCPageListState<FollowRoomRes, SCRoomFollowPage> {
String? lastId;
bool _isLoading = true; //
@override
void initState() {
@ -38,10 +40,22 @@ class _RoomFollowPageState
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.transparent,
body: buildList(context),
body:
items.isEmpty && isLoading
? const SCHomeSkeletonShimmer(
builder: _buildFollowSkeletonContent,
)
: buildList(context),
);
}
static Widget _buildFollowSkeletonContent(
BuildContext context,
double progress,
) {
return buildHomeRoomGridSkeleton(progress);
}
@override
Widget buildItem(FollowRoomRes roomRes) {
return GestureDetector(
@ -101,12 +115,19 @@ class _RoomFollowPageState
SizedBox(width: 5.w),
Expanded(
child: SizedBox(
height: 21.w,
height: 17.w,
child: Align(
alignment: Alignment.centerLeft,
child: Transform.translate(
offset: Offset(0, -0.6.w),
child: text(
roomRes.roomProfile?.roomName ?? "",
fontSize: 15.sp,
fontSize: 13.sp,
textColor: Color(0xffffffff),
fontWeight: FontWeight.w400,
lineHeight: 1,
),
),
),
// (roomRes.roomProfile?.roomName?.length ?? 0) > 10
// ? Marquee(
@ -150,11 +171,7 @@ class _RoomFollowPageState
?.password
?.isEmpty ??
false)
? Image.asset(
"sc_images/general/sc_icon_online_user.png",
width: 14.w,
height: 14.w,
)
? SCRoomLiveAudioIndicator(width: 14.w, height: 14.w)
: Image.asset(
"sc_images/index/sc_icon_room_suo.png",
width: 20.w,
@ -178,7 +195,8 @@ class _RoomFollowPageState
false)
? text(
roomRes.roomProfile?.extValues?.memberQuantity ?? "0",
fontSize: 12.sp,
fontSize: 10.sp,
lineHeight: 1,
)
: Container(height: 10.w),
SizedBox(width: 10.w),
@ -275,11 +293,6 @@ class _RoomFollowPageState
if (onErr != null) {
onErr();
}
} finally {
//
setState(() {
_isLoading = false;
});
}
}
}

View File

@ -1,4 +1,3 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:yumi/app_localizations.dart';
@ -7,21 +6,24 @@ import 'package:provider/provider.dart';
import 'package:yumi/ui_kit/components/sc_compontent.dart';
import 'package:yumi/ui_kit/components/sc_page_list.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart';
import 'package:yumi/ui_kit/widgets/room/room_live_audio_indicator.dart';
import 'package:yumi/app/constants/sc_screen.dart';
import 'package:yumi/shared/business_logic/models/res/follow_room_res.dart';
import 'package:yumi/services/general/sc_app_general_manager.dart';
import 'package:yumi/services/audio/rtc_manager.dart';
import '../mine/sc_home_mine_skeleton.dart';
///
class SCRoomHistoryPage extends SCPageList {
const SCRoomHistoryPage({super.key});
@override
_SCRoomHistoryPageState createState() => _SCRoomHistoryPageState();
SCPageListState createState() => _SCRoomHistoryPageState();
}
class _SCRoomHistoryPageState
extends SCPageListState<FollowRoomRes, SCRoomHistoryPage> {
String? lastId;
bool _isLoading = true; //
@override
void initState() {
@ -38,10 +40,22 @@ class _SCRoomHistoryPageState
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.transparent,
body: buildList(context),
body:
items.isEmpty && isLoading
? const SCHomeSkeletonShimmer(
builder: _buildHistorySkeletonContent,
)
: buildList(context),
);
}
static Widget _buildHistorySkeletonContent(
BuildContext context,
double progress,
) {
return buildHomeRoomGridSkeleton(progress);
}
@override
Widget buildItem(FollowRoomRes roomRes) {
return GestureDetector(
@ -101,12 +115,19 @@ class _SCRoomHistoryPageState
SizedBox(width: 5.w),
Expanded(
child: SizedBox(
height: 21.w,
height: 17.w,
child: Align(
alignment: Alignment.centerLeft,
child: Transform.translate(
offset: Offset(0, -0.6.w),
child: text(
roomRes.roomProfile?.roomName ?? "",
fontSize: 15.sp,
fontSize: 13.sp,
textColor: Color(0xffffffff),
fontWeight: FontWeight.w400,
lineHeight: 1,
),
),
),
// (roomRes.roomProfile?.roomName?.length ?? 0) > 10
@ -151,11 +172,7 @@ class _SCRoomHistoryPageState
?.password
?.isEmpty ??
false)
? Image.asset(
"sc_images/general/sc_icon_online_user.png",
width: 14.w,
height: 14.w,
)
? SCRoomLiveAudioIndicator(width: 14.w, height: 14.w)
: Image.asset(
"sc_images/index/sc_icon_room_suo.png",
width: 20.w,
@ -179,7 +196,8 @@ class _SCRoomHistoryPageState
false)
? text(
roomRes.roomProfile?.extValues?.memberQuantity ?? "0",
fontSize: 12.sp,
fontSize: 10.sp,
lineHeight: 1,
)
: Container(height: 10.w),
SizedBox(width: 10.w),
@ -275,11 +293,6 @@ class _SCRoomHistoryPageState
if (onErr != null) {
onErr();
}
} finally {
//
setState(() {
_isLoading = false;
});
}
}
}

View File

@ -1,49 +1,46 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:yumi/app_localizations.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/shared/tools/sc_loading_manager.dart';
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
import 'package:yumi/ui_kit/components/sc_page_list.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart';
import 'package:marquee/marquee.dart';
import 'package:provider/provider.dart';
import '../../../../app/config/business_logic_strategy.dart';
import '../../../../app/constants/sc_global_config.dart';
import '../../../../shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
import '../../../../shared/business_logic/models/res/follow_room_res.dart';
import '../../../../services/general/sc_app_general_manager.dart';
import '../../../../services/room/rc_room_manager.dart';
import '../../../../services/audio/rtc_manager.dart';
import '../../../../services/room/rc_room_manager.dart';
import '../../../../ui_kit/components/sc_compontent.dart';
import '../../../../ui_kit/theme/socialchat_theme.dart';
import '../follow/sc_room_follow_page.dart';
import '../history/sc_room_history_page.dart';
import 'sc_home_mine_skeleton.dart';
class SCHomeMinePage extends StatefulWidget {
const SCHomeMinePage({super.key});
class SCHomeMinePage extends SCPageList {
@override
_HomeMinePageState createState() => _HomeMinePageState();
State<SCHomeMinePage> createState() => _HomeMinePageState();
}
class _HomeMinePageState extends SCPageListState<FollowRoomRes, SCHomeMinePage>
class _HomeMinePageState extends State<SCHomeMinePage>
with SingleTickerProviderStateMixin {
List<FollowRoomRes> historyRooms = [];
String? lastId;
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
late TabController _tabController;
final List<Widget> _pages = [SCRoomHistoryPage(), SCRoomFollowPage()];
bool isLoading = false;
final List<Widget> _pages = const [SCRoomHistoryPage(), SCRoomFollowPage()];
final List<Widget> _tabs = [];
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
enablePullUp = true;
backgroundColor = Colors.transparent;
loadData(1);
Provider.of<SocialChatRoomManager>(
context,
listen: false,
).fetchMyRoomData();
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
@ -54,20 +51,13 @@ class _HomeMinePageState extends SCPageListState<FollowRoomRes, SCHomeMinePage>
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
SizedBox(width: 15.w),
text(
SCAppLocalizations.of(context)!.myRoom,
fontSize: 16.sp,
fontWeight: FontWeight.bold,
textColor: Colors.white,
),
],
),
SizedBox(height: 5.w),
Consumer<SocialChatRoomManager>(
builder: (_, provider, __) {
if (provider.isMyRoomLoading && provider.myRoom == null) {
return const SCHomeSkeletonShimmer(
builder: _buildMineRoomSkeletonContent,
);
}
return Container(
margin: EdgeInsets.symmetric(horizontal: 12.w),
decoration: BoxDecoration(
@ -125,7 +115,14 @@ class _HomeMinePageState extends SCPageListState<FollowRoomRes, SCHomeMinePage>
);
}
_buildMyRoom(SocialChatRoomManager provider) {
static Widget _buildMineRoomSkeletonContent(
BuildContext context,
double progress,
) {
return buildHomeMyRoomSkeleton(progress);
}
Widget _buildMyRoom(SocialChatRoomManager provider) {
return provider.myRoom != null
? GestureDetector(
behavior: HitTestBehavior.opaque,
@ -296,312 +293,4 @@ class _HomeMinePageState extends SCPageListState<FollowRoomRes, SCHomeMinePage>
},
);
}
void _loadOtherData() {
SCLoadingManager.show();
Provider.of<SocialChatRoomManager>(
context,
listen: false,
).fetchMyRoomData();
SCAccountRepository()
.trace()
.then((res) {
historyRooms = res;
SCLoadingManager.hide();
setState(() {});
})
.catchError((_) {
SCLoadingManager.hide();
});
}
_buildHistoryRoomItem(FollowRoomRes roomRes) {
return GestureDetector(
child: SizedBox(
width: 70.w,
height: 70.w,
child: Stack(
children: [
netImage(
url: resolveRoomCoverUrl(
roomRes.roomProfile?.id,
roomRes.roomProfile?.roomCover,
),
defaultImg: kRoomCoverDefaultImg,
width: 70.w,
height: 70.w,
fit: BoxFit.cover,
borderRadius: BorderRadius.circular(8.w),
),
getRoomCoverHeaddress(roomRes).isNotEmpty
? Transform.translate(
offset: Offset(0, -2.w),
child: Transform.scale(
scaleX: 1.1,
scaleY: 1.12,
child: Image.asset(getRoomCoverHeaddress(roomRes)),
),
)
: Container(),
(roomRes.roomProfile?.roomGameIcon?.isNotEmpty ?? false)
? PositionedDirectional(
top: 8.w,
end: 8.w,
child: netImage(
url: roomRes.roomProfile?.roomGameIcon ?? "",
width: 15.w,
height: 15.w,
borderRadius: BorderRadius.circular(4.w),
),
)
: Container(),
PositionedDirectional(
top: 3.w,
end: 5.w,
child:
(roomRes
.roomProfile
?.extValues
?.roomSetting
?.password
?.isNotEmpty ??
false)
? Image.asset(
"sc_images/index/sc_icon_room_suo.png",
width: 16.w,
height: 16.w,
)
: Container(),
),
],
),
),
onTap: () {
Provider.of<RtcProvider>(
context,
listen: false,
).joinVoiceRoomSession(context, roomRes.roomProfile?.id ?? "");
},
);
}
@override
Widget buildItem(FollowRoomRes res) {
return SCDebounceWidget(
child: SizedBox(
height: 105.w,
child: Stack(
alignment: Alignment.center,
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(13.w),
border: Border.all(
color: _strategy.getExploreRoomBorderColor(),
width: _strategy.getExploreRoomBorderWidth().w,
),
),
margin: EdgeInsetsDirectional.symmetric(
horizontal: 10.w,
vertical: 3.w,
),
),
Row(
children: [
SizedBox(width: 20.w),
netImage(
url: resolveRoomCoverUrl(
res.roomProfile?.id,
res.roomProfile?.roomCover,
),
defaultImg: kRoomCoverDefaultImg,
fit: BoxFit.cover,
borderRadius: BorderRadius.all(Radius.circular(12.w)),
width: 85.w,
height: 85.w,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 15.w),
Row(
children: [
SizedBox(width: 8.w),
Consumer<SCAppGeneralManager>(
builder: (_, provider, __) {
return netImage(
url:
"${provider.findCountryByName(res.roomProfile?.countryName ?? "")?.nationalFlag}",
width: 25.w,
height: 15.w,
borderRadius: BorderRadius.all(
Radius.circular(3.w),
),
);
},
),
SizedBox(width: 5.w),
Container(
constraints: BoxConstraints(
maxHeight: 21.w,
maxWidth: 150.w,
),
child:
(res.roomProfile?.roomName?.length ?? 0) >
_strategy
.getExploreRoomNameMarqueeThreshold()
? Marquee(
text: res.roomProfile?.roomName ?? "",
style: TextStyle(
fontSize: 15.sp,
color: Colors.black,
fontWeight: FontWeight.bold,
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(
res.roomProfile?.roomName ?? "",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 15.sp,
color: Colors.black,
fontWeight: FontWeight.bold,
decoration: TextDecoration.none,
),
),
),
],
),
SizedBox(height: 3.w),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(width: 8.w),
text(
"ID:${res.roomProfile?.roomAccount}",
fontWeight: FontWeight.w600,
textColor: Colors.black,
fontSize: 15.sp,
),
],
),
SizedBox(height: 3.w),
Row(
children: [
SizedBox(width: 8.w),
Image.asset(
"sc_images/msg/sc_icon_message_activity.png",
height: 25.w,
width: 25.w,
),
SizedBox(width: 4.w),
Container(
constraints: BoxConstraints(
maxHeight: 21.w,
maxWidth: 170.w,
),
child:
(res.roomProfile?.roomDesc?.length ?? 0) >
_strategy
.getExploreRoomDescMarqueeThreshold()
? Marquee(
text: res.roomProfile?.roomDesc ?? "",
style: TextStyle(
fontSize: 15.sp,
color: Colors.black,
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(
res.roomProfile?.roomDesc ?? "",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 15.sp,
color: Colors.black,
decoration: TextDecoration.none,
),
),
),
],
),
],
),
],
),
],
),
),
onTap: () {
Provider.of<RtcProvider>(
context,
listen: false,
).joinVoiceRoomSession(context, res.roomProfile?.id ?? "");
},
);
}
@override
builderDivider() {
// return Divider(
// height: 1.w,
// color: Color(0xff3D3277).withOpacity(0.5),
// indent: 15.w,
// );
return Container(height: 8.w);
}
///
@override
loadPage({
required int page,
required Function(List<FollowRoomRes>) onSuccess,
Function? onErr,
}) async {
_loadOtherData();
if (page == 1) {
lastId = null;
}
try {
var roomList = await SCAccountRepository().followRoomList(lastId: lastId);
if (roomList.isNotEmpty) {
lastId = roomList.last.id;
}
onSuccess(roomList);
} catch (e) {
if (onErr != null) {
onErr();
}
}
}
String getRoomCoverHeaddress(FollowRoomRes roomRes) {
return "";
}
}

View File

@ -0,0 +1,293 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
const Duration _kHomeMineSkeletonAnimationDuration = Duration(
milliseconds: 1450,
);
const Color _kHomeMineSkeletonShell = Color(0xFF0F3730);
const Color _kHomeMineSkeletonShellStrong = Color(0xFF1A4A41);
const Color _kHomeMineSkeletonBoneBase = Color(0xFF2B5C53);
const Color _kHomeMineSkeletonBoneHighlight = Color(0xFF5F8177);
const Color _kHomeMineSkeletonBorder = Color(0x66D8B57B);
class SCHomeSkeletonShimmer extends StatefulWidget {
const SCHomeSkeletonShimmer({super.key, required this.builder});
final Widget Function(BuildContext context, double progress) builder;
@override
State<SCHomeSkeletonShimmer> createState() => _SCHomeSkeletonShimmerState();
}
class _SCHomeSkeletonShimmerState extends State<SCHomeSkeletonShimmer>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: _kHomeMineSkeletonAnimationDuration,
)..repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) => widget.builder(context, _controller.value),
);
}
}
BoxDecoration buildHomeSkeletonShellDecoration({
required double radius,
EdgeInsetsGeometry? padding,
}) {
return BoxDecoration(
borderRadius: BorderRadius.circular(radius),
border: Border.all(color: _kHomeMineSkeletonBorder, width: 1.w),
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [_kHomeMineSkeletonShellStrong, _kHomeMineSkeletonShell],
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.16),
blurRadius: 16.w,
offset: Offset(0, 8.w),
),
],
);
}
Widget buildHomeSkeletonBlock(
double progress, {
double? width,
required double height,
BorderRadius? borderRadius,
BoxShape shape = BoxShape.rectangle,
}) {
final double slideOffset = (progress * 2) - 1;
return Container(
width: width,
height: height,
decoration: BoxDecoration(
shape: shape,
borderRadius:
shape == BoxShape.circle
? null
: (borderRadius ?? BorderRadius.circular(10.w)),
gradient: LinearGradient(
begin: Alignment(-1.4 + slideOffset, -0.2),
end: Alignment(1.4 + slideOffset, 0.2),
colors: const [
_kHomeMineSkeletonBoneBase,
_kHomeMineSkeletonBoneBase,
_kHomeMineSkeletonBoneHighlight,
_kHomeMineSkeletonBoneBase,
],
stops: const [0.0, 0.36, 0.54, 1.0],
),
),
);
}
Widget buildHomeMyRoomSkeleton(double progress) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 12.w),
width: double.infinity,
height: 85.w,
decoration: buildHomeSkeletonShellDecoration(radius: 16.w),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.w),
child: Row(
children: [
buildHomeSkeletonBlock(
progress,
width: 70.w,
height: 70.w,
borderRadius: BorderRadius.circular(10.w),
),
SizedBox(width: 10.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
buildHomeSkeletonBlock(
progress,
width: 18.w,
height: 14.w,
borderRadius: BorderRadius.circular(3.w),
),
SizedBox(width: 5.w),
buildHomeSkeletonBlock(
progress,
width: 110.w,
height: 14.w,
borderRadius: BorderRadius.circular(999.w),
),
],
),
SizedBox(height: 9.w),
buildHomeSkeletonBlock(
progress,
width: 170.w,
height: 12.w,
borderRadius: BorderRadius.circular(999.w),
),
SizedBox(height: 8.w),
buildHomeSkeletonBlock(
progress,
width: 76.w,
height: 10.w,
borderRadius: BorderRadius.circular(999.w),
),
],
),
),
SizedBox(width: 10.w),
buildHomeSkeletonBlock(
progress,
width: 24.w,
height: 24.w,
shape: BoxShape.circle,
),
],
),
),
);
}
Widget buildHomeRoomGridSkeleton(
double progress, {
int itemCount = 6,
EdgeInsetsGeometry? padding,
}) {
return GridView.builder(
shrinkWrap: true,
padding: padding ?? EdgeInsets.symmetric(horizontal: 5.w),
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
itemCount: itemCount,
itemBuilder: (context, index) => buildHomeRoomCardSkeleton(progress),
);
}
Widget buildHomeRoomCardSkeleton(double progress) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14.w),
border: Border.all(color: _kHomeMineSkeletonBorder, 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: [
_kHomeMineSkeletonShellStrong,
_kHomeMineSkeletonShell,
],
),
),
),
),
Positioned.fill(
child: Padding(
padding: EdgeInsets.only(bottom: 34.w),
child: buildHomeSkeletonBlock(
progress,
height: double.infinity,
borderRadius: BorderRadius.vertical(top: Radius.circular(14.w)),
),
),
),
Positioned(
top: 12.w,
right: 12.w,
child: buildHomeSkeletonBlock(
progress,
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: [
buildHomeSkeletonBlock(
progress,
width: 20.w,
height: 13.w,
borderRadius: BorderRadius.circular(3.w),
),
SizedBox(width: 6.w),
Expanded(
child: buildHomeSkeletonBlock(
progress,
height: 10.w,
borderRadius: BorderRadius.circular(999.w),
),
),
SizedBox(width: 8.w),
buildHomeSkeletonBlock(
progress,
width: 18.w,
height: 10.w,
borderRadius: BorderRadius.circular(999.w),
),
],
),
),
),
],
),
),
);
}

View File

@ -1,9 +1,7 @@
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/cupertino.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:marquee/marquee.dart';
import 'package:provider/provider.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../../../../app_localizations.dart';
@ -13,15 +11,25 @@ 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/general/sc_app_general_manager.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
_HomePartyPageState createState() => _HomePartyPageState();
State<SCHomePartyPage> createState() => _HomePartyPageState();
}
class _HomePartyPageState extends State<SCHomePartyPage>
@ -29,18 +37,31 @@ class _HomePartyPageState extends State<SCHomePartyPage>
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(
@ -73,41 +94,13 @@ class _HomePartyPageState extends State<SCHomePartyPage>
),
Consumer<SCAppGeneralManager>(
builder: (context, ref, child) {
if (_shouldShowLeaderboardSkeleton(ref)) {
return _buildLeaderboardSkeleton();
}
return _banner2(ref);
},
),
rooms.isEmpty
? GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
loadData();
},
child:
isLoading
? Center(
child: CupertinoActivityIndicator(
color: Colors.white24,
),
)
: mainEmpty(
msg: SCAppLocalizations.of(context)!.noData,
),
)
: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // 2
childAspectRatio: 1, //
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
padding: EdgeInsets.all(10),
itemCount: rooms!.length,
itemBuilder: (context, index) {
return _buildItem(rooms[index], index);
},
),
_buildRoomsSection(),
],
),
),
@ -153,7 +146,6 @@ class _HomePartyPageState extends State<SCHomePartyPage>
borderRadius: BorderRadius.circular(12.w),
),
onTap: () {
print('ads:${item.toJson()}');
SCBannerUtils.openBanner(item, context);
},
);
@ -384,9 +376,303 @@ class _HomePartyPageState extends State<SCHomePartyPage>
);
}
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)
@ -403,8 +689,15 @@ class _HomePartyPageState extends State<SCHomePartyPage>
isLoading = false;
if (mounted) setState(() {});
});
Provider.of<SCAppGeneralManager>(context, listen: false).loadMainBanner();
Provider.of<SCAppGeneralManager>(context, listen: false).appLeaderboard();
generalManager.loadMainBanner();
generalManager.appLeaderboard().whenComplete(() {
if (!mounted) {
return;
}
setState(() {
_isLeaderboardLoading = false;
});
});
}
_buildItem(SocialChatRoomRes res, int index) {
@ -462,12 +755,19 @@ class _HomePartyPageState extends State<SCHomePartyPage>
SizedBox(width: 5.w),
Expanded(
child: SizedBox(
height: 21.w,
height: 17.w,
child: Align(
alignment: Alignment.centerLeft,
child: Transform.translate(
offset: Offset(0, -0.6.w),
child: text(
res.roomName ?? "",
fontSize: 15.sp,
fontSize: 13.sp,
textColor: Color(0xffffffff),
fontWeight: FontWeight.w400,
lineHeight: 1,
),
),
),
// (roomRes.roomProfile?.roomName?.length ?? 0) > 10
// ? Marquee(
@ -505,11 +805,7 @@ class _HomePartyPageState extends State<SCHomePartyPage>
),
SizedBox(width: 5.w),
(res.extValues?.roomSetting?.password?.isEmpty ?? false)
? Image.asset(
"sc_images/general/sc_icon_online_user.png",
width: 14.w,
height: 14.w,
)
? SCRoomLiveAudioIndicator(width: 14.w, height: 14.w)
: Image.asset(
"sc_images/index/sc_icon_room_suo.png",
width: 20.w,
@ -521,7 +817,8 @@ class _HomePartyPageState extends State<SCHomePartyPage>
(res.extValues?.roomSetting?.password?.isEmpty ?? false)
? text(
res.extValues?.memberQuantity ?? "0",
fontSize: 12.sp,
fontSize: 10.sp,
lineHeight: 1,
)
: Container(height: 10.w),
SizedBox(width: 10.w),

View File

@ -96,8 +96,16 @@ class _SCIndexPageState extends State<SCIndexPage> {
fit: BoxFit.fill,
),
),
child: Theme(
data: Theme.of(context).copyWith(
splashFactory: NoSplash.splashFactory,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
),
child: BottomNavigationBar(
elevation: 0,
enableFeedback: false,
backgroundColor: Colors.transparent,
selectedLabelStyle: TextStyle(
fontWeight: FontWeight.w600,
@ -119,6 +127,7 @@ class _SCIndexPageState extends State<SCIndexPage> {
),
),
),
),
],
),
);

View File

@ -29,6 +29,7 @@ import '../../ui_kit/components/sc_compontent.dart';
import '../../ui_kit/components/sc_debounce_widget.dart';
import '../../ui_kit/components/sc_page_list.dart';
import '../../ui_kit/components/text/sc_text.dart';
import '../../ui_kit/widgets/room/room_live_audio_indicator.dart';
///
class SearchPage extends SCPageList {
@ -485,12 +486,19 @@ class _SearchRoomListState extends State<SearchRoomList> {
SizedBox(width: 5.w),
Expanded(
child: SizedBox(
height: 21.w,
height: 17.w,
child: Align(
alignment: Alignment.centerLeft,
child: Transform.translate(
offset: Offset(0, -0.6.w),
child: text(
e.roomName ?? "",
fontSize: 15.sp,
fontSize: 13.sp,
textColor: Color(0xffffffff),
fontWeight: FontWeight.w400,
lineHeight: 1,
),
),
),
// (roomRes.roomProfile?.roomName?.length ?? 0) > 10
@ -529,11 +537,7 @@ class _SearchRoomListState extends State<SearchRoomList> {
),
SizedBox(width: 5.w),
(e.extValues?.roomSetting?.password?.isEmpty ?? false)
? Image.asset(
"sc_images/general/sc_icon_online_user.png",
width: 14.w,
height: 14.w,
)
? SCRoomLiveAudioIndicator(width: 14.w, height: 14.w)
: Image.asset(
"sc_images/index/sc_icon_room_suo.png",
width: 20.w,
@ -543,7 +547,11 @@ class _SearchRoomListState extends State<SearchRoomList> {
? SizedBox(width: 3.w)
: Container(height: 10.w),
(e.extValues?.roomSetting?.password?.isEmpty ?? false)
? text(e.extValues?.memberQuantity ?? "0", fontSize: 12.sp)
? text(
e.extValues?.memberQuantity ?? "0",
fontSize: 10.sp,
lineHeight: 1,
)
: Container(height: 10.w),
SizedBox(width: 10.w),
],

View File

@ -1,10 +1,8 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:yumi/app_localizations.dart';
import 'package:yumi/ui_kit/components/sc_compontent.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart';
import 'package:yumi/ui_kit/components/sc_tts.dart';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:provider/provider.dart';
@ -15,6 +13,7 @@ import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repositor
import 'package:yumi/shared/business_logic/models/res/store_list_res.dart';
import 'package:yumi/services/auth/user_profile_manager.dart';
import 'package:yumi/ui_kit/widgets/store/props_store_chatbox_detail_dialog.dart';
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
import '../../../shared/data_sources/models/enum/sc_currency_type.dart';
import '../../../shared/data_sources/models/enum/sc_props_type.dart';
@ -51,7 +50,10 @@ class _StoreChatboxPageState
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: buildList(context),
body:
items.isEmpty && isLoading
? const SCStoreGridSkeleton()
: buildList(context),
);
}
@ -62,7 +64,7 @@ class _StoreChatboxPageState
decoration: BoxDecoration(
color:
res.isSelecte
? Color(0xff18F2B1).withOpacity(0.1)
? const Color(0xff18F2B1).withValues(alpha: 0.1)
: SCGlobalConfig.businessLogicStrategy
.getStoreItemBackgroundColor(),
border: Border.all(
@ -87,16 +89,11 @@ class _StoreChatboxPageState
fit: BoxFit.contain,
),
SizedBox(height: 3.w),
Row(
mainAxisSize: MainAxisSize.min,
children: [
text(
buildStoreBagItemTitle(
res.res.propsResources?.name ?? "",
textColor: Colors.white,
fontSize: 11.sp,
),
],
),
SizedBox(height: 3.w),
Row(
mainAxisSize: MainAxisSize.min,
@ -195,7 +192,7 @@ class _StoreChatboxPageState
alignment: Alignment.bottomCenter,
animationType: SmartAnimationType.fade,
builder: (_) {
return PropsStoreChatboxDetailDialog(res,disCount,);
return PropsStoreChatboxDetailDialog(res, disCount);
},
);
}

View File

@ -1,11 +1,8 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:yumi/app_localizations.dart';
import 'package:yumi/ui_kit/components/sc_compontent.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart';
import 'package:yumi/ui_kit/components/sc_tts.dart';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/shared/tools/sc_loading_manager.dart';
@ -15,6 +12,7 @@ import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repositor
import 'package:yumi/shared/business_logic/models/res/store_list_res.dart';
import 'package:yumi/services/auth/user_profile_manager.dart';
import 'package:yumi/ui_kit/widgets/store/props_store_headdress_detail_dialog.dart';
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
import '../../../shared/data_sources/models/enum/sc_currency_type.dart';
import '../../../shared/data_sources/models/enum/sc_props_type.dart';
@ -51,7 +49,10 @@ class _StoreHeaddressPageState
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: buildList(context),
body:
items.isEmpty && isLoading
? const SCStoreGridSkeleton()
: buildList(context),
);
}
@ -62,7 +63,7 @@ class _StoreHeaddressPageState
decoration: BoxDecoration(
color:
res.isSelecte
? Color(0xff18F2B1).withOpacity(0.1)
? const Color(0xff18F2B1).withValues(alpha: 0.1)
: SCGlobalConfig.businessLogicStrategy
.getStoreItemBackgroundColor(),
border: Border.all(
@ -87,16 +88,11 @@ class _StoreHeaddressPageState
height: 55.w,
),
SizedBox(height: 3.w),
Row(
mainAxisSize: MainAxisSize.min,
children: [
text(
buildStoreBagItemTitle(
res.res.propsResources?.name ?? "",
textColor: Colors.white,
fontSize: 11.sp,
),
],
),
SizedBox(height: 3.w),
Row(
mainAxisSize: MainAxisSize.min,
@ -161,7 +157,6 @@ class _StoreHeaddressPageState
onTap: () {
_selectItem(res);
_showDetail(res.res);
},
);
}

View File

@ -1,16 +1,14 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
import 'package:yumi/app_localizations.dart';
import 'package:yumi/ui_kit/components/sc_compontent.dart';
import 'package:yumi/ui_kit/components/sc_page_list.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart';
import 'package:yumi/shared/business_logic/models/res/store_list_res.dart';
import 'package:yumi/ui_kit/widgets/store/props_store_mountains_detail_dialog.dart';
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
import 'package:yumi/modules/store/headdress/store_headdress_page.dart';
import '../../../shared/data_sources/models/enum/sc_currency_type.dart';
@ -55,7 +53,10 @@ class _StoreMountainsPageState
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: buildList(context),
body:
items.isEmpty && isLoading
? const SCStoreGridSkeleton()
: buildList(context),
);
}
@ -66,7 +67,7 @@ class _StoreMountainsPageState
decoration: BoxDecoration(
color:
res.isSelecte
? Color(0xff18F2B1).withOpacity(0.1)
? const Color(0xff18F2B1).withValues(alpha: 0.1)
: SCGlobalConfig.businessLogicStrategy
.getStoreItemBackgroundColor(),
border: Border.all(
@ -91,16 +92,11 @@ class _StoreMountainsPageState
height: 55.w,
),
SizedBox(height: 3.w),
Row(
mainAxisSize: MainAxisSize.min,
children: [
text(
buildStoreBagItemTitle(
res.res.propsResources?.name ?? "",
textColor: Colors.white,
fontSize: 11.sp,
),
],
),
SizedBox(height: 3.w),
Row(
mainAxisSize: MainAxisSize.min,
@ -114,7 +110,8 @@ class _StoreMountainsPageState
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Image.asset(
SCGlobalConfig.businessLogicStrategy.getStoreItemGoldIcon(),
SCGlobalConfig.businessLogicStrategy
.getStoreItemGoldIcon(),
width: 22.w,
),
),
@ -124,7 +121,9 @@ class _StoreMountainsPageState
text: "${res.res.propsPrices![0].amount}",
style: TextStyle(
fontSize: 12.sp,
color: SCGlobalConfig.businessLogicStrategy.getStoreItemPriceTextColor(),
color:
SCGlobalConfig.businessLogicStrategy
.getStoreItemPriceTextColor(),
fontWeight: FontWeight.w600,
decoration: TextDecoration.lineThrough,
),
@ -136,7 +135,9 @@ class _StoreMountainsPageState
"${((res.res.propsPrices![0].amount ?? 0) * disCount).toInt()}/${res.res.propsPrices![0].days} ${SCAppLocalizations.of(context)!.day}",
style: TextStyle(
fontSize: 12.sp,
color: SCGlobalConfig.businessLogicStrategy.getStoreItemPriceTextColor(),
color:
SCGlobalConfig.businessLogicStrategy
.getStoreItemPriceTextColor(),
fontWeight: FontWeight.w600,
),
),
@ -164,7 +165,6 @@ class _StoreMountainsPageState
);
}
///
@override
loadPage({

View File

@ -1,23 +1,18 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
import 'package:provider/provider.dart';
import 'package:yumi/app_localizations.dart';
import 'package:yumi/ui_kit/components/sc_compontent.dart';
import 'package:yumi/ui_kit/components/sc_page_list.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart';
import 'package:yumi/ui_kit/components/sc_tts.dart';
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart';
import 'package:yumi/shared/business_logic/models/res/store_list_res.dart';
import 'package:yumi/services/auth/user_profile_manager.dart';
import 'package:yumi/ui_kit/widgets/store/props_store_theme_detail_dialog.dart';
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
import 'package:yumi/modules/store/headdress/store_headdress_page.dart';
import '../../../shared/data_sources/models/enum/sc_currency_type.dart';
@ -25,7 +20,6 @@ import '../../../shared/data_sources/models/enum/sc_props_type.dart';
///
class StoreThemePage extends SCPageList {
StoreThemePage();
@override
@ -34,8 +28,6 @@ class StoreThemePage extends SCPageList {
class _StoreThemePagePageState
extends SCPageListState<StoreListResBean, StoreThemePage> {
//
double disCount = 1;
@ -60,7 +52,10 @@ class _StoreThemePagePageState
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: buildList(context),
body:
items.isEmpty && isLoading
? const SCStoreGridSkeleton()
: buildList(context),
);
}
@ -71,7 +66,7 @@ class _StoreThemePagePageState
decoration: BoxDecoration(
color:
res.isSelecte
? Color(0xff18F2B1).withOpacity(0.1)
? const Color(0xff18F2B1).withValues(alpha: 0.1)
: SCGlobalConfig.businessLogicStrategy
.getStoreItemBackgroundColor(),
border: Border.all(
@ -97,16 +92,11 @@ class _StoreThemePagePageState
borderRadius: BorderRadius.all(Radius.circular(8.w)),
),
SizedBox(height: 3.w),
Row(
mainAxisSize: MainAxisSize.min,
children: [
text(
buildStoreBagItemTitle(
res.res.propsResources?.name ?? "",
textColor: Colors.white,
fontSize: 11.sp,
),
],
),
SizedBox(height: 3.w),
Row(
mainAxisSize: MainAxisSize.min,
@ -120,7 +110,8 @@ class _StoreThemePagePageState
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Image.asset(
SCGlobalConfig.businessLogicStrategy.getStoreItemGoldIcon(),
SCGlobalConfig.businessLogicStrategy
.getStoreItemGoldIcon(),
width: 22.w,
),
),
@ -130,7 +121,9 @@ class _StoreThemePagePageState
text: "${res.res.propsPrices![0].amount}",
style: TextStyle(
fontSize: 12.sp,
color: SCGlobalConfig.businessLogicStrategy.getStoreItemPriceTextColor(),
color:
SCGlobalConfig.businessLogicStrategy
.getStoreItemPriceTextColor(),
fontWeight: FontWeight.w600,
decoration: TextDecoration.lineThrough,
),
@ -142,7 +135,9 @@ class _StoreThemePagePageState
"${((res.res.propsPrices![0].amount ?? 0) * disCount).toInt()}/${res.res.propsPrices![0].days} ${SCAppLocalizations.of(context)!.day}",
style: TextStyle(
fontSize: 12.sp,
color: SCGlobalConfig.businessLogicStrategy.getStoreItemPriceTextColor(),
color:
SCGlobalConfig.businessLogicStrategy
.getStoreItemPriceTextColor(),
fontWeight: FontWeight.w600,
),
),
@ -181,7 +176,6 @@ class _StoreThemePagePageState
);
}
///
@override
loadPage({

View File

@ -1,4 +1,3 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@ -19,6 +18,7 @@ import 'package:yumi/services/auth/user_profile_manager.dart';
import 'package:yumi/ui_kit/widgets/bag/props_bag_chatbox_detail_dialog.dart';
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
import 'package:yumi/modules/store/store_route.dart';
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
import '../../../../shared/data_sources/models/enum/sc_props_type.dart';
import '../../../../ui_kit/components/sc_debounce_widget.dart';
@ -60,7 +60,12 @@ class _BagsChatboxPageState
backgroundColor: Colors.transparent,
body: Column(
children: [
Expanded(child: buildList(context)),
Expanded(
child:
items.isEmpty && isLoading
? const SCBagGridSkeleton()
: buildList(context),
),
selecteChatbox != null ? SizedBox(height: 10.w) : Container(),
selecteChatbox != null
? Container(
@ -69,7 +74,7 @@ class _BagsChatboxPageState
topLeft: Radius.circular(12.w),
topRight: Radius.circular(12.w),
),
color: Color(0xff18F2B1).withOpacity(0.1),
color: const Color(0xff18F2B1).withValues(alpha: 0.1),
),
child: Column(
children: [
@ -215,7 +220,7 @@ class _BagsChatboxPageState
return GestureDetector(
child: Container(
decoration: BoxDecoration(
color:Color(0xff18F2B1).withOpacity(0.1),
color: const Color(0xff18F2B1).withValues(alpha: 0.1),
border: Border.all(
color:
selecteChatbox?.propsResources?.id == res.propsResources?.id
@ -237,18 +242,13 @@ class _BagsChatboxPageState
fit: BoxFit.contain,
),
SizedBox(height: 8.w),
Row(
mainAxisSize: MainAxisSize.min,
children: [
text(
buildStoreBagItemTitle(
res.propsResources?.name ?? "",
textColor: Colors.black,
fontSize: 12.sp,
),
],
),
],
),
PositionedDirectional(
top: 0,
start: 0,

View File

@ -19,6 +19,7 @@ import 'package:yumi/ui_kit/components/sc_tts.dart';
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
import 'package:yumi/ui_kit/widgets/bag/props_bag_headdress_detail_dialog.dart';
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
import 'package:provider/provider.dart';
import '../../../../shared/data_sources/models/enum/sc_props_type.dart';
@ -59,7 +60,12 @@ class _BagsHeaddressPageState
backgroundColor: Colors.transparent,
body: Column(
children: [
Expanded(child: buildList(context)),
Expanded(
child:
items.isEmpty && isLoading
? const SCBagGridSkeleton()
: buildList(context),
),
selecteHeaddress != null ? SizedBox(height: 10.w) : Container(),
selecteHeaddress != null
? Container(
@ -68,11 +74,11 @@ class _BagsHeaddressPageState
topLeft: Radius.circular(12.w),
topRight: Radius.circular(12.w),
),
color: Color(0xff18F2B1).withOpacity(0.1),
color: const Color(0xff18F2B1).withValues(alpha: 0.1),
),
child: Column(
children: [
SizedBox(height: 5.w,),
SizedBox(height: 5.w),
Row(
spacing: 5.w,
mainAxisAlignment: MainAxisAlignment.center,
@ -128,9 +134,11 @@ class _BagsHeaddressPageState
height: 35.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(55.w),
color: myHeaddress?.id ==
color:
myHeaddress?.id ==
selecteHeaddress?.propsResources?.id
?Colors.white:SocialChatTheme.primaryLight,
? Colors.white
: SocialChatTheme.primaryLight,
),
child: text(
myHeaddress?.id ==
@ -212,7 +220,7 @@ class _BagsHeaddressPageState
return GestureDetector(
child: Container(
decoration: BoxDecoration(
color:Color(0xff18F2B1).withOpacity(0.1),
color: const Color(0xff18F2B1).withValues(alpha: 0.1),
border: Border.all(
color:
selecteHeaddress?.propsResources?.id == res.propsResources?.id
@ -234,18 +242,13 @@ class _BagsHeaddressPageState
height: 55.w,
),
SizedBox(height: 8.w),
Row(
mainAxisSize: MainAxisSize.min,
children: [
text(
buildStoreBagItemTitle(
res.propsResources?.name ?? "",
textColor: Colors.white,
fontSize: 12.sp,
),
],
),
],
),
PositionedDirectional(
top: 0,
start: 0,

View File

@ -1,4 +1,3 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@ -18,6 +17,7 @@ import 'package:yumi/shared/business_logic/models/res/login_res.dart';
import 'package:yumi/services/auth/user_profile_manager.dart';
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
import 'package:yumi/modules/store/store_route.dart';
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
import '../../../../shared/data_sources/models/enum/sc_props_type.dart';
import '../../../../ui_kit/components/sc_debounce_widget.dart';
@ -66,7 +66,12 @@ class _BagsMountainsPageState
backgroundColor: Colors.transparent,
body: Column(
children: [
Expanded(child: buildList(context)),
Expanded(
child:
items.isEmpty && isLoading
? const SCBagGridSkeleton()
: buildList(context),
),
selecteMountains != null ? SizedBox(height: 10.w) : Container(),
selecteMountains != null
? Container(
@ -75,7 +80,7 @@ class _BagsMountainsPageState
topLeft: Radius.circular(12.w),
topRight: Radius.circular(12.w),
),
color: Color(0xff18F2B1).withOpacity(0.1),
color: const Color(0xff18F2B1).withValues(alpha: 0.1),
),
child: Column(
children: [
@ -221,7 +226,7 @@ class _BagsMountainsPageState
return GestureDetector(
child: Container(
decoration: BoxDecoration(
color:Color(0xff18F2B1).withOpacity(0.1),
color: const Color(0xff18F2B1).withValues(alpha: 0.1),
border: Border.all(
color:
selecteMountains?.propsResources?.id == res.propsResources?.id
@ -243,18 +248,13 @@ class _BagsMountainsPageState
height: 55.w,
),
SizedBox(height: 8.w),
Row(
mainAxisSize: MainAxisSize.min,
children: [
text(
buildStoreBagItemTitle(
res.propsResources?.name ?? "",
textColor: Colors.white,
fontSize: 12.sp,
),
],
),
],
),
PositionedDirectional(
top: 0,
start: 0,

View File

@ -1,5 +1,4 @@
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@ -14,6 +13,7 @@ import 'package:yumi/ui_kit/widgets/bag/props_bag_theme_detail_dialog.dart';
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
import 'package:yumi/modules/store/store_route.dart';
import 'package:provider/provider.dart';
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
import '../../../../app/constants/sc_room_msg_type.dart';
import '../../../../shared/tools/sc_loading_manager.dart';
import '../../../../shared/business_logic/models/res/sc_room_theme_list_res.dart';
@ -57,7 +57,12 @@ class _BagsThemePageState
backgroundColor: Colors.transparent,
body: Column(
children: [
Expanded(child: buildList(context)),
Expanded(
child:
items.isEmpty && isLoading
? const SCBagGridSkeleton(showTitle: false)
: buildList(context),
),
selecteTheme != null ? SizedBox(height: 10.w) : Container(),
selecteTheme != null
? Container(
@ -66,7 +71,7 @@ class _BagsThemePageState
topLeft: Radius.circular(12.w),
topRight: Radius.circular(12.w),
),
color: Color(0xff18F2B1).withOpacity(0.1),
color: const Color(0xff18F2B1).withValues(alpha: 0.1),
),
child: Column(
children: [
@ -208,7 +213,7 @@ class _BagsThemePageState
return GestureDetector(
child: Container(
decoration: BoxDecoration(
color: Color(0xff18F2B1).withOpacity(0.1),
color: const Color(0xff18F2B1).withValues(alpha: 0.1),
border: Border.all(
color:
selecteTheme?.id == res.id

View File

@ -1,7 +1,11 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_config_repository_imp.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
import 'package:yumi/shared/data_sources/models/country_mode.dart';
import 'package:yumi/shared/business_logic/models/res/sc_banner_leaderboard_res.dart';
import 'package:yumi/shared/business_logic/models/res/country_res.dart';
@ -14,13 +18,17 @@ import '../../shared/data_sources/models/enum/sc_gift_type.dart';
class SCAppGeneralManager extends ChangeNotifier {
List<CountryMode> countryModeList = [];
Map<String, List<Country>> _countryMap = {};
Map<String, Country> _countryByNameMap = {};
final Map<String, List<Country>> _countryMap = {};
final Map<String, Country> _countryByNameMap = {};
bool _isFetchingCountryList = false;
///
List<SocialChatGiftRes> giftResList = [];
Map<String, SocialChatGiftRes> _giftByIdMap = {};
final Map<String, SocialChatGiftRes> _giftByIdMap = {};
final Set<String> _warmedGiftCoverUrls = <String>{};
final Set<String> _warmingGiftCoverUrls = <String>{};
bool _isFetchingGiftList = false;
bool _hasGiftListLoaded = false;
Map<String, List<SocialChatGiftRes>> giftByTab = {};
Map<String, List<SocialChatGiftRes>> activityGiftByTab = {};
@ -38,6 +46,8 @@ class SCAppGeneralManager extends ChangeNotifier {
// configLevel();
}
bool get isGiftListLoading => _isFetchingGiftList && !_hasGiftListLoaded;
///
Future<void> fetchCountryList() async {
if (_isFetchingCountryList) {
@ -116,10 +126,47 @@ class SCAppGeneralManager extends ChangeNotifier {
Scaffold.of(context).closeDrawer();
}
void giftList({bool includeCustomized = true}) async {
Future<void> giftList({
bool includeCustomized = true,
bool forceRefresh = false,
}) async {
if (_isFetchingGiftList) {
return;
}
if (_hasGiftListLoaded && giftResList.isNotEmpty && !forceRefresh) {
_rebuildGiftTabs(includeCustomized: includeCustomized);
notifyListeners();
unawaited(
warmGiftPageCovers("ALL", pageIndex: 0, pageCount: 2, pageSize: 8),
);
return;
}
_isFetchingGiftList = true;
if (!_hasGiftListLoaded) {
notifyListeners();
}
try {
giftResList = await SCChatRoomRepository().giftList();
_hasGiftListLoaded = true;
_rebuildGiftTabs(includeCustomized: includeCustomized);
notifyListeners();
///
downLoad(giftResList);
unawaited(
warmGiftPageCovers("ALL", pageIndex: 0, pageCount: 2, pageSize: 8),
);
} catch (_) {
return;
} finally {
_isFetchingGiftList = false;
notifyListeners();
}
}
void _rebuildGiftTabs({required bool includeCustomized}) {
giftByTab.clear();
_giftByIdMap.clear();
for (var gift in giftResList) {
giftByTab[gift.giftTab];
var gmap = giftByTab[gift.giftTab];
@ -148,12 +195,53 @@ class SCAppGeneralManager extends ChangeNotifier {
} else {
giftByTab.remove("CUSTOMIZED");
}
}
notifyListeners();
Future<void> warmGiftPageCovers(
String type, {
required int pageIndex,
int pageCount = 1,
int pageSize = 8,
}) async {
final gifts = giftByTab[type] ?? <SocialChatGiftRes>[];
if (gifts.isEmpty) {
return;
}
final start = pageIndex * pageSize;
if (start >= gifts.length) {
return;
}
final end = math.min(gifts.length, start + (pageSize * pageCount));
await warmGiftCoverImages(gifts.sublist(start, end));
}
///
downLoad(giftResList);
} catch (e) {}
Future<void> warmGiftCoverImages(
Iterable<SocialChatGiftRes> gifts, {
int maxConcurrency = 4,
}) async {
final pending = <Future<void>>[];
for (final gift in gifts) {
final coverUrl = gift.giftPhoto ?? "";
if (coverUrl.isEmpty ||
_warmedGiftCoverUrls.contains(coverUrl) ||
!_warmingGiftCoverUrls.add(coverUrl)) {
continue;
}
pending.add(() async {
final warmed = await warmNetworkImage(coverUrl);
_warmingGiftCoverUrls.remove(coverUrl);
if (warmed) {
_warmedGiftCoverUrls.add(coverUrl);
}
}());
if (pending.length >= maxConcurrency) {
await Future.wait(pending);
pending.clear();
}
}
if (pending.isNotEmpty) {
await Future.wait(pending);
}
}
void giftActivityList() async {
@ -168,14 +256,18 @@ class SCAppGeneralManager extends ChangeNotifier {
activityGiftByTab["${gift.activityId}"] = gmap;
}
notifyListeners();
} catch (e) {}
} catch (_) {
return;
}
}
///
void giftBackpack() async {
try {
var result = await SCChatRoomRepository().giftBackpack();
} catch (e) {}
await SCChatRoomRepository().giftBackpack();
} catch (_) {
return;
}
}
///id获取礼物
@ -202,7 +294,7 @@ class SCAppGeneralManager extends ChangeNotifier {
List<SCIndexBannerRes> gameBanners = [];
///banner
void loadMainBanner() async {
Future<void> loadMainBanner() async {
try {
var banners = await SCConfigRepositoryImp().getBanner();
homeBanners.clear();
@ -227,7 +319,9 @@ class SCAppGeneralManager extends ChangeNotifier {
}
}
notifyListeners();
} catch (e) {}
} catch (_) {
return;
}
}
Map<String, List<Emojis>> emojiByTab = {};
@ -249,10 +343,12 @@ class SCAppGeneralManager extends ChangeNotifier {
SCBannerLeaderboardRes? appLeaderResult;
///
void appLeaderboard() async {
Future<void> appLeaderboard() async {
try {
appLeaderResult = await SCChatRoomRepository().appLeaderboard();
notifyListeners();
} catch (e) {}
} catch (_) {
return;
}
}
}

View File

@ -12,11 +12,29 @@ import 'package:yumi/shared/business_logic/models/res/sc_room_contribute_level_r
class SocialChatRoomManager extends ChangeNotifier {
MyRoomRes? myRoom;
SCRoomContributeLevelRes? roomContributeLevelRes;
bool isMyRoomLoading = false;
bool hasLoadedMyRoom = false;
void fetchMyRoomData() async {
Future<void> fetchMyRoomData() async {
if (isMyRoomLoading) {
return;
}
isMyRoomLoading = true;
if (!hasLoadedMyRoom) {
myRoom = null;
myRoom = await SCAccountRepository().myProfile();
}
notifyListeners();
try {
myRoom = await SCAccountRepository().myProfile();
} catch (_) {
if (!hasLoadedMyRoom) {
myRoom = null;
}
} finally {
isMyRoomLoading = false;
hasLoadedMyRoom = true;
notifyListeners();
}
}
void updateMyRoomInfo(SCEditRoomInfoRes roomInfo) {
@ -43,6 +61,8 @@ class SocialChatRoomManager extends ChangeNotifier {
}
await SCAccountRepository().createRoom();
myRoom = await SCAccountRepository().myProfile();
hasLoadedMyRoom = true;
isMyRoomLoading = false;
if (!context.mounted) {
notifyListeners();
SCLoadingManager.hide();

View File

@ -0,0 +1,85 @@
import 'dart:async';
import 'dart:io';
import 'package:extended_image/extended_image.dart';
import 'package:flutter/painting.dart';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
bool _requiresAuthenticatedImageHeaders(String url) {
return url.contains("/external/oss/local/");
}
Map<String, String>? buildNetworkImageHeaders(String url) {
if (!_requiresAuthenticatedImageHeaders(url)) {
return null;
}
final uri = Uri.tryParse(url);
final headers = <String, String>{
"req-lang": SCGlobalConfig.lang,
"X-Forwarded-Proto": "http",
"User-Agent": "Dart/3.7.2(dart:io)",
"req-imei": SCGlobalConfig.imei,
"req-app-intel":
"version=${SCGlobalConfig.version};build=${SCGlobalConfig.build};model=${SCGlobalConfig.model};sysVersion=${SCGlobalConfig.sysVersion};channel=${SCGlobalConfig.channel}",
"req-client": Platform.isAndroid ? "Android" : "iOS",
"req-sys-origin":
"origin=${SCGlobalConfig.origin};originChild=${SCGlobalConfig.originChild}",
};
if ((uri?.host ?? "").isNotEmpty) {
headers["Host"] = uri!.host;
}
final token = AccountStorage().getToken();
if (token.isNotEmpty) {
headers["Authorization"] = "Bearer $token";
}
headers.removeWhere((key, value) => value.trim().isEmpty);
return headers;
}
ImageProvider<Object> buildCachedImageProvider(String url) {
if (url.startsWith("http")) {
return ExtendedNetworkImageProvider(
url,
headers: buildNetworkImageHeaders(url),
cache: true,
cacheMaxAge: const Duration(days: 7),
);
}
return FileImage(File(url));
}
Future<bool> warmNetworkImage(
String url, {
Duration timeout = const Duration(seconds: 6),
}) async {
if (url.isEmpty) {
return false;
}
final provider = buildCachedImageProvider(url);
final stream = provider.resolve(ImageConfiguration.empty);
final completer = Completer<bool>();
late final ImageStreamListener listener;
listener = ImageStreamListener(
(image, synchronousCall) {
if (!completer.isCompleted) {
completer.complete(true);
}
stream.removeListener(listener);
},
onError: (Object error, StackTrace? stackTrace) {
if (!completer.isCompleted) {
completer.complete(false);
}
stream.removeListener(listener);
},
);
stream.addListener(listener);
return completer.future.timeout(
timeout,
onTimeout: () {
stream.removeListener(listener);
return false;
},
);
}

View File

@ -1,7 +1,4 @@
import 'dart:io';
import 'package:extended_image/extended_image.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart';
@ -9,48 +6,17 @@ import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
import 'package:tancent_vap/utils/constant.dart';
import 'package:tancent_vap/widgets/vap_view.dart';
import 'package:yumi/shared/data_sources/sources/local/file_cache_manager.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/ui_kit/widgets/headdress/headdress_widget.dart';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/app/constants/sc_screen.dart';
import 'package:yumi/shared/tools/sc_path_utils.dart';
import 'package:yumi/shared/tools/sc_room_profile_cache.dart';
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
import '../../shared/data_sources/models/enum/sc_room_roles_type.dart';
const String kRoomCoverDefaultImg = "sc_images/general/sc_no_data.png";
bool _requiresAuthenticatedImageHeaders(String url) {
return url.contains("/external/oss/local/");
}
Map<String, String>? _buildImageHeaders(String url) {
if (!_requiresAuthenticatedImageHeaders(url)) {
return null;
}
final uri = Uri.tryParse(url);
final headers = <String, String>{
"req-lang": SCGlobalConfig.lang,
"X-Forwarded-Proto": "http",
"User-Agent": "Dart/3.7.2(dart:io)",
"req-imei": SCGlobalConfig.imei,
"req-app-intel":
"version=${SCGlobalConfig.version};build=${SCGlobalConfig.build};model=${SCGlobalConfig.model};sysVersion=${SCGlobalConfig.sysVersion};channel=${SCGlobalConfig.channel}",
"req-client": Platform.isAndroid ? "Android" : "iOS",
"req-sys-origin":
"origin=${SCGlobalConfig.origin};originChild=${SCGlobalConfig.originChild}",
};
if ((uri?.host ?? "").isNotEmpty) {
headers["Host"] = uri!.host;
}
final token = AccountStorage().getToken();
if (token.isNotEmpty) {
headers["Authorization"] = "Bearer $token";
}
headers.removeWhere((key, value) => value.trim().isEmpty);
return headers;
}
String resolveRoomCoverUrl(String? roomId, String? roomCover) {
final cache = SCRoomProfileCache.getRoomProfile(roomId ?? "");
return SCRoomProfileCache.preferCachedValue(cache["roomCover"], roomCover) ??
@ -212,13 +178,15 @@ Widget netImage({
BoxFit? fit,
BoxShape? shape,
Border? border,
Widget? loadingWidget,
Widget? errorWidget,
}) {
// print('${SCGlobalConfig.imgHost}$image?imageslim');
return ExtendedImage.network(
url,
width: width,
height: height ?? width,
headers: _buildImageHeaders(url),
headers: buildNetworkImageHeaders(url),
fit: fit ?? BoxFit.cover,
cache: true,
shape: shape ?? BoxShape.rectangle,
@ -234,6 +202,9 @@ Widget netImage({
fit: fit ?? BoxFit.cover,
);
} else if (state.extendedImageLoadState == LoadState.failed) {
if (errorWidget != null) {
return errorWidget;
}
return noDefaultImg
? Container()
: Image.asset(
@ -241,6 +212,9 @@ Widget netImage({
fit: BoxFit.cover,
);
} else {
if (loadingWidget != null) {
return loadingWidget;
}
return noDefaultImg
? Container()
: Image.asset(

View File

@ -12,6 +12,7 @@ import 'package:yumi/shared/business_logic/models/res/join_room_res.dart';
import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:yumi/app/constants/sc_screen.dart';
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
import 'package:yumi/ui_kit/widgets/room/room_live_audio_indicator.dart';
//
Offset kDefaultFloatOffset = Offset(
@ -191,11 +192,7 @@ class _FloatRoomWindowState extends State<FloatRoomWindow> {
height: 40.w,
borderRadius: BorderRadius.circular(8.w),
),
Image.asset(
"sc_images/general/sc_icon_online_user.png",
width: 15.w,
height: 15.w,
),
SCRoomLiveAudioIndicator(width: 15.w, height: 15.w),
],
),
SizedBox(width: width(5)),

View File

@ -0,0 +1,137 @@
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class SCRoomLiveAudioIndicator extends StatefulWidget {
const SCRoomLiveAudioIndicator({
super.key,
this.width,
this.height,
this.color = const Color(0xFF1BF2AE),
this.duration = const Duration(milliseconds: 1260),
});
final double? width;
final double? height;
final Color color;
final Duration duration;
@override
State<SCRoomLiveAudioIndicator> createState() =>
_SCRoomLiveAudioIndicatorState();
}
class _SCRoomLiveAudioIndicatorState extends State<SCRoomLiveAudioIndicator>
with SingleTickerProviderStateMixin {
static const List<double> _phaseOffsets = <double>[0.0, 0.18, 0.36];
static const List<double> _minFactors = <double>[0.28, 0.2, 0.34];
static const List<double> _maxFactors = <double>[0.9, 0.76, 0.96];
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: widget.duration)
..repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final double indicatorWidth = widget.width ?? 14.w;
final double indicatorHeight = widget.height ?? 14.w;
return RepaintBoundary(
child: SizedBox(
width: indicatorWidth,
height: indicatorHeight,
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: _RoomLiveAudioPainter(
progress: _controller.value,
color: widget.color,
phaseOffsets: _phaseOffsets,
minFactors: _minFactors,
maxFactors: _maxFactors,
),
);
},
),
),
);
}
}
class _RoomLiveAudioPainter extends CustomPainter {
const _RoomLiveAudioPainter({
required this.progress,
required this.color,
required this.phaseOffsets,
required this.minFactors,
required this.maxFactors,
});
final double progress;
final Color color;
final List<double> phaseOffsets;
final List<double> minFactors;
final List<double> maxFactors;
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()..style = PaintingStyle.fill;
final double spacing = size.width * 0.16;
final double barWidth = (size.width - (spacing * 2)) / 3;
final double radius = math.min(barWidth / 2, size.height / 2);
final double baseline = size.height;
for (int index = 0; index < 3; index++) {
final double cycleProgress = (progress + phaseOffsets[index]) % 1.0;
final double wave = _buildWaveValue(cycleProgress);
final double factor =
lerpDouble(minFactors[index], maxFactors[index], wave) ??
minFactors[index];
final double barHeight = math.max(
size.height * factor,
size.height * 0.2,
);
final double left = index * (barWidth + spacing);
final double top = baseline - barHeight;
paint.color = color.withValues(alpha: 0.68 + (0.32 * wave));
canvas.drawRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(left, top, barWidth, barHeight),
Radius.circular(radius),
),
paint,
);
}
}
@override
bool shouldRepaint(covariant _RoomLiveAudioPainter oldDelegate) {
return oldDelegate.progress != progress || oldDelegate.color != color;
}
double _buildWaveValue(double t) {
final double primary = 0.5 - (0.5 * math.cos(2 * math.pi * t));
final double smoothPrimary = primary * primary * (3 - (2 * primary));
final double detail = 0.5 - (0.5 * math.cos(4 * math.pi * t));
final double detailWeight = (1 - ((smoothPrimary - 0.5).abs() * 2)).clamp(
0.0,
1.0,
);
return (smoothPrimary + ((detail - 0.5) * 0.16 * detailWeight)).clamp(
0.0,
1.0,
);
}
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:provider/provider.dart';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/ui_kit/components/sc_compontent.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart';
@ -72,6 +73,10 @@ class _RoomOnlineUserWidgetState extends State<RoomOnlineUserWidget> {
"",
width: 23.w,
height: 23.w,
defaultImg:
SCGlobalConfig
.businessLogicStrategy
.getMePageDefaultAvatarImage(),
shape: BoxShape.circle,
border: Border.all(
color: Colors.white,

View File

@ -0,0 +1,288 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart';
const Duration _kStoreBagSkeletonAnimationDuration = Duration(
milliseconds: 1450,
);
const Color _kStoreBagSkeletonShell = Color(0xFF0F3730);
const Color _kStoreBagSkeletonShellStrong = Color(0xFF1A4A41);
const Color _kStoreBagSkeletonBoneBase = Color(0xFF2B5C53);
const Color _kStoreBagSkeletonBoneHighlight = Color(0xFF5F8177);
const Color _kStoreBagSkeletonBorder = Color(0x66D8B57B);
Widget buildStoreBagItemTitle(
String content, {
required double fontSize,
Color textColor = Colors.white,
EdgeInsetsGeometry? padding,
}) {
return Padding(
padding: padding ?? EdgeInsets.symmetric(horizontal: 8.w),
child: SizedBox(
width: double.infinity,
child: text(
content,
fontSize: fontSize,
textColor: textColor,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
);
}
class _SCStoreBagSkeletonShimmer extends StatefulWidget {
const _SCStoreBagSkeletonShimmer({required this.builder});
final Widget Function(BuildContext context, double progress) builder;
@override
State<_SCStoreBagSkeletonShimmer> createState() =>
_SCStoreBagSkeletonShimmerState();
}
class _SCStoreBagSkeletonShimmerState extends State<_SCStoreBagSkeletonShimmer>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: _kStoreBagSkeletonAnimationDuration,
)..repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) => widget.builder(context, _controller.value),
);
}
}
class SCStoreGridSkeleton extends StatelessWidget {
const SCStoreGridSkeleton({super.key, this.itemCount = 9});
final int itemCount;
@override
Widget build(BuildContext context) {
return _SCStoreBagSkeletonShimmer(
builder:
(context, progress) => GridView.builder(
padding: EdgeInsets.symmetric(horizontal: 6.w),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 12.w,
crossAxisSpacing: 12.w,
childAspectRatio: 0.78,
),
itemCount: itemCount,
itemBuilder: (context, index) => _buildStoreCardSkeleton(progress),
),
);
}
}
class SCBagGridSkeleton extends StatelessWidget {
const SCBagGridSkeleton({
super.key,
this.itemCount = 9,
this.showTitle = true,
});
final int itemCount;
final bool showTitle;
@override
Widget build(BuildContext context) {
return _SCStoreBagSkeletonShimmer(
builder:
(context, progress) => GridView.builder(
padding: EdgeInsets.symmetric(horizontal: 6.w),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 12.w,
crossAxisSpacing: 12.w,
childAspectRatio: 0.88,
),
itemCount: itemCount,
itemBuilder:
(context, index) =>
_buildBagCardSkeleton(progress, showTitle: showTitle),
),
);
}
}
Widget _buildStoreCardSkeleton(double progress) {
return DecoratedBox(
decoration: _buildStoreBagShellDecoration(radius: 8.w),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.w),
child: Column(
children: [
SizedBox(height: 2.w),
_buildStoreBagSkeletonBone(
progress,
width: 55.w,
height: 55.w,
borderRadius: BorderRadius.circular(12.w),
),
SizedBox(height: 10.w),
_buildStoreBagSkeletonBone(
progress,
width: double.infinity,
height: 12.w,
borderRadius: BorderRadius.circular(999.w),
),
SizedBox(height: 10.w),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildStoreBagSkeletonBone(
progress,
width: 16.w,
height: 16.w,
shape: BoxShape.circle,
),
SizedBox(width: 6.w),
_buildStoreBagSkeletonBone(
progress,
width: 56.w,
height: 11.w,
borderRadius: BorderRadius.circular(999.w),
),
],
),
SizedBox(height: 6.w),
_buildStoreBagSkeletonBone(
progress,
width: 48.w,
height: 11.w,
borderRadius: BorderRadius.circular(999.w),
),
],
),
),
);
}
Widget _buildBagCardSkeleton(double progress, {required bool showTitle}) {
return DecoratedBox(
decoration: _buildStoreBagShellDecoration(radius: 8.w),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 12.w),
child: Stack(
children: [
PositionedDirectional(
top: 0,
start: 0,
child: _buildStoreBagSkeletonBone(
progress,
width: 60.w,
height: 18.w,
borderRadius: BorderRadiusDirectional.only(
topStart: Radius.circular(6.w),
topEnd: Radius.circular(12.w),
bottomEnd: Radius.circular(12.w),
),
),
),
PositionedDirectional(
bottom: 0,
end: 0,
child: _buildStoreBagSkeletonBone(
progress,
width: 18.w,
height: 18.w,
shape: BoxShape.circle,
),
),
Column(
children: [
SizedBox(height: 24.w),
_buildStoreBagSkeletonBone(
progress,
width: 55.w,
height: 55.w,
borderRadius: BorderRadius.circular(12.w),
),
if (showTitle) ...[
SizedBox(height: 10.w),
_buildStoreBagSkeletonBone(
progress,
width: double.infinity,
height: 12.w,
borderRadius: BorderRadius.circular(999.w),
),
],
],
),
],
),
),
);
}
BoxDecoration _buildStoreBagShellDecoration({required double radius}) {
return BoxDecoration(
borderRadius: BorderRadius.circular(radius),
border: Border.all(color: _kStoreBagSkeletonBorder, width: 1.w),
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [_kStoreBagSkeletonShellStrong, _kStoreBagSkeletonShell],
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.16),
blurRadius: 16.w,
offset: Offset(0, 8.w),
),
],
);
}
Widget _buildStoreBagSkeletonBone(
double progress, {
double? width,
required double height,
BorderRadiusGeometry? borderRadius,
BoxShape shape = BoxShape.rectangle,
}) {
final double slideOffset = (progress * 2) - 1;
return Container(
width: width,
height: height,
decoration: BoxDecoration(
shape: shape,
borderRadius:
shape == BoxShape.circle
? null
: (borderRadius ?? BorderRadius.circular(10.w)),
gradient: LinearGradient(
begin: Alignment(-1.4 + slideOffset, -0.2),
end: Alignment(1.4 + slideOffset, 0.2),
colors: const [
_kStoreBagSkeletonBoneBase,
_kStoreBagSkeletonBoneBase,
_kStoreBagSkeletonBoneHighlight,
_kStoreBagSkeletonBoneBase,
],
stops: const [0.0, 0.36, 0.54, 1.0],
),
),
);
}

View File

@ -9,6 +9,7 @@
- 已定位并修复语言房礼物飘屏资源引用错误:代码里误写 `sc_icon_gift_flosc_bg` / `sc_icon_luck_gift_flosc_*`,实际资源文件名为 `float`,导致点击送礼后飘屏背景图加载失败,相关动画无法正常显示。
- 已继续定位语言房礼物特效不播放的根因:后端礼物配置返回的特效标记为 `ANIMATION`,而前端历史代码只识别拼写错误的 `ANIMSCION`,导致送礼后直接跳过全屏/半屏特效播放;现已改为同时兼容两种标记。
- 已继续优化语言房礼物特效播放性能:将礼物资源预热改为“后台串行预加载 + 选中礼物高优先级预加载 + 播放阶段复用同一解码/下载任务”,避免送礼时重复下载或重复解码同一个 `.svga/.mp4`;同时为全屏播放器增加 `RepaintBoundary`,并改为按原始比例渲染、关闭越界绘制,减少整页重绘和过度放大带来的卡顿。
- 已继续优化礼物二级页首屏与翻页加载体验:礼物配置改为会话内复用,避免每次打开礼物面板都重新拉列表;首屏与翻页页卡增加整页骨架态,页面图片预热改为“当前页并发预热 + 下一页提前预热”,并增加“最短可感知骨架时长 + 最长骨架保护窗口”,避免整页骨架一闪而过;超过上限后再切换到真实卡片,并让单个礼物继续使用小 `loading` 占位补齐,整体交互对齐常见直播 app 的礼物面板体验。
- 已修复首页房间封面显示链路:兼容接口 `roomCover/cover` 双字段,并补齐编辑房间成功后的封面内存回写。
- 已继续修复房间设置保存封面后丢失的问题:房间保存接口改为兼容提交 `id/roomId``roomCover/cover`,并在保存成功后优先保留刚提交的封面、名称、公告,避免被空响应覆盖。
- 已新增按房间 ID 的本地房间资料缓存兜底:首页、我的房间、历史/关注、搜索和再次进入房间都会优先回填最近一次本地保存的封面;同时已将房间封面空态图替换为新的 `sc_no_data.png`
@ -18,6 +19,12 @@
- 已修复静态头像框透明区域误变黑的问题:调整头像框白底透明化公式后,进入房间时不再出现只有头像附近有颜色、其余房间背景被黑块覆盖的现象。
- 已修复个人主页首屏黑屏与内容区多余圆角问题:为个人页补充稳定的底图/底色占位,避免进入页面时先出现黑屏;同时移除资料内容区顶部多余圆角。
- 已将个人主页首屏占位升级为骨架屏进入个人页时会先按真实版式展示头像、昵称、计数区、Tab 和资料内容的骨架块,不再先露默认背景等待约 1 秒。
- 已为首页 Party 页补齐首屏骨架屏:顶部 H5 榜单现按三列奖杯卡位展示头像/标题骨架,下方房间区改为双列房卡骨架,并采用深绿底 + 金色描边的项目内风格;同时拆分榜单与房间列表加载态,仅在“首屏无内容加载”时展示骨架,避免下拉刷新时整页闪烁。
- 已为首页 My 板块补齐首屏骨架屏:`My room` 顶部房间卡片改为独立加载态,避免首屏把“正在加载我的房间”误显示成“去创建房间”;`Recent/Followed` 两个子页也已改成同风格双列房卡骨架,进入 tab 时会先展示完整结构占位,再按真实数据切换。
- 已为 `Me` 入口下的 `Store / Bag` 页面补齐首屏骨架屏:四类 `store` 商品页和四类 `bag` 物品页在“首屏空数据加载”阶段都会先展示同风格的三列卡片骨架,不再只显示小菊花;同时已为商品/物品名称补齐宽度约束与省略号处理,避免超长文案顶出卡片。
- 已继续细化首页房卡体验:`My` 板块房卡骨架数量已从 `4` 个补齐到 `6` 个,与首页 Party 首屏保持一致;同时将房卡右下角原本的静态绿色“直播中”图片替换为代码驱动的三段式动态音频条,首页、我的、搜索结果和房间浮窗现都会展示跳动效果,不再依赖静态资源图;最新已把动效调整为“单周期完整峰谷 + A/B/C 错峰起伏”的连续波形,观感更接近直播音频电平。
- 已继续微调首页房卡信息密度:房卡底部房名与在线人数字号已统一调小,和动态音频条更协调;同时已移除 `My` 板块顶部 `my room` 标题及其占位,让房间卡片整体上移,对齐更紧凑。
- 已继续微调首页视觉细节房卡底栏房名再次缩小并轻微上移和国旗、动态音频条、人数的中线更一致首页顶部排行骨架也已简化为“3 个圆形头像 + 1 根文本条”,更贴近真实卡片结构。
- 已优化语言房顶部成员入口的点击范围:右上角在线成员区域现已将左侧头像堆叠区与右侧成员图标统一为同一点击热区,用户点击头像区也可直接打开成员列表页。
- 已继续修复房间封面跨用户不同步问题:当列表或进房返回空封面时,客户端会自动补拉 `room/profile/specific` 详情并缓存真实封面;同时房主保存房间资料后会向房间内其他用户派发资料刷新消息,避免只有自己能看到新封面、其他用户仍显示 `no data`
- 已修复个人主页编辑页头像上传后无变化的问题:上传成功后会先本地回写新头像地址并立即提交;资料更新接口现同时兼容 `userAvatar/avatar` 字段,且不再被紧接着的旧资料回拉覆盖,返回个人主页后头像会即时更新。
@ -71,6 +78,7 @@
## 已改动文件
- `需求进度.md`
- `lib/shared/data_sources/models/enum/sc_gift_type.dart`
- `lib/shared/tools/sc_network_image_utils.dart`
- `lib/shared/tools/sc_gift_vap_svga_manager.dart`
- `lib/services/general/sc_app_general_manager.dart`
- `lib/ui_kit/widgets/room/floating/floating_gift_screen_widget.dart`
@ -91,10 +99,21 @@
- `lib/ui_kit/widgets/room/room_head_widget.dart`
- `lib/modules/room/detail/room_detail_page.dart`
- `lib/modules/home/popular/party/sc_home_party_page.dart`
- `lib/modules/home/popular/mine/sc_home_mine_skeleton.dart`
- `lib/modules/home/popular/follow/sc_room_follow_page.dart`
- `lib/modules/home/popular/history/sc_room_history_page.dart`
- `lib/modules/home/popular/mine/sc_home_mine_page.dart`
- `lib/ui_kit/widgets/room/room_live_audio_indicator.dart`
- `lib/modules/search/sc_search_page.dart`
- `lib/modules/store/headdress/store_headdress_page.dart`
- `lib/modules/store/mountains/store_mountains_page.dart`
- `lib/modules/store/theme/store_theme_page.dart`
- `lib/modules/store/chatbox/store_chatbox_page.dart`
- `lib/modules/user/my_items/headdress/bags_headdress_page.dart`
- `lib/modules/user/my_items/mountains/bags_mountains_page.dart`
- `lib/modules/user/my_items/chatbox/bags_chatbox_page.dart`
- `lib/modules/user/my_items/theme/bags_theme_page.dart`
- `lib/ui_kit/widgets/store/store_bag_page_helpers.dart`
- `sc_images/general/sc_no_data.png`
- `.gitignore`
- `android/key.properties`
@ -125,6 +144,7 @@
- 已确认当前“点击赠送没有动画”至少包含一类前端资源问题:大额礼物/幸运礼物飘屏 widget 已进入渲染,但因为背景图 asset 路径写错而无法显示;全屏 `SVGA/VAP` 特效是否播放仍取决于具体礼物是否带有 `giftSourceUrl`,且 `special` 包含 `ANIMSCION/ANIMATION/GLOBAL_GIFT` 之一。
- 已完成一轮针对卡顿的代码级优化:礼物列表现在只会对真正需要全屏特效的礼物做受控预热;用户选中礼物时会立即高优先级预热该礼物;播放器播放时优先复用内存里的 `MovieEntity` 或同一路径的在途任务,不再重复发起网络下载/重复解码;渲染层也已改为独立重绘边界以减少房间整页跟随重绘。
- 已通过 GiftFX 调试日志确认:示例礼物 `阿拉伯舞蹈` 实际返回 `giftSourceUrl=.svga``special=ANIMATION`,此前前端因为不识别该标记而输出 `skip local play because special does not include ANIMSCION/GLOBAL_GIFT`;本轮已补齐 `ANIMATION` 兼容判断。
- 礼物二级页当前已按“整页骨架 + 当前页并发预热 + 下一页提前预热 + 单卡小 loading 占位”的方式重构首屏加载策略:首次进入和翻页时不会再直接暴露 `noData` 图,且整页骨架会保留一个可感知时长,不再一闪而过;当前页礼物封面会并发预热而不是等每个格子各自串行体感加载。
- 当前工作区存在用户已有未提交改动,后续处理均避开覆盖。
- 首页 `party/follow/history/mine` 以及房间详情使用的房间封面模型已兼容 `roomCover``cover` 两种返回字段,上传封面后不会再因为字段名不一致掉回空占位。
- 房间设置保存封面时,前端现在会同时兼容提交和读取两套字段,并在 `PUT /room/profile` 响应或紧接着的房间详情刷新返回空封面时保留刚上传的封面,不再被空值冲掉。
@ -136,6 +156,12 @@
- 静态头像框的透明区域现已保持透明,不会再被错误算成黑色;房间麦位头像、个人主页主头像和其它复用 `head()` 的头像组件进入房间后不会再出现大块黑底遮罩。
- 个人主页现在在数据加载前也会先展示默认背景和底色不会再闪黑“About me / Giftwall” 内容区顶部边缘已改为直角,不再额外出现一个圆角缺口。
- 个人主页当前已接入贴合页面结构的骨架屏,占位期间不会先看到背景图单独显示;资料接口返回后再无缝切到真实内容,首屏观感更平滑。
- 首页 Party 页当前已接入贴合真实版式的骨架屏:进入首页首屏时会先看到三张排行卡位与双列房卡占位,骨架配色已跟随页面深绿+鎏金主视觉,不再只显示居中的白色 `loading`;且排行榜与房间区会按各自请求结果独立切换,先返回的区域会先显示真实内容。
- 首页 My 板块当前已接入贴合真实版式的骨架屏:顶部 `My room` 现区分“首次加载中”和“确实未创建房间”,不会再先闪出创建房间卡;`Recent/Followed` 在首屏空数据加载时也会先展示双列房卡骨架,不再只显示小菊花或局部空白。
- `Me` 入口下的 `Store / Bag` 页面当前也已接入深绿主题的三列卡片骨架:首次进入并且列表尚未返回时,会先显示与正式卡片比例一致的占位;接口返回后再切到真实商品/物品卡片。`store``bag` 卡片标题现都带宽度约束和单行省略,类似截图里的长名称不会再横向撑破布局。
- 首页与搜索等房卡右下角的在线状态图标当前已改为代码绘制的动态音频条:三根绿条会按不同相位走完整的波峰波谷周期,错峰连续起伏,视觉上更接近直播/语聊房的实时状态;`My` 板块骨架数量也已与 Party 页对齐为 `6` 个。
- 首页房卡底部信息当前已进一步收紧:房名和人数文字比上一版更小,底栏与动态音频条的视觉重心更一致;`My` 页顶部 `my room` 标题也已移除,下方卡片和 tab 会自然上移填充。
- 首页顶部排行骨架当前已进一步收窄为单条标题占位,不再保留两条文本骨架;房卡底栏房名也已再次缩一号并做轻微上移,对齐更贴近设计稿观感。
- 目录体积排查显示:`build/``4.7G``.dart_tool/``347M``ios/``250M``sc_images/``48M``local_packages/``6.1M`
- 当前包体过大的主要原因已经确认:
- universal APK 带入了 `arm64-v8a``armeabi-v7a``x86_64` 三套 ABI。