From db3edcefb42ac578e3796a006fce0320fd001a71 Mon Sep 17 00:00:00 2001 From: NIGGER SLAYER Date: Wed, 15 Apr 2026 11:44:49 +0800 Subject: [PATCH] =?UTF-8?q?=E9=AA=A8=E6=9E=B6=E5=B1=8F=E4=BB=A5=E5=8F=8A?= =?UTF-8?q?=E4=B8=80=E4=BA=9B=E5=B7=B2=E7=9F=A5bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/gift/gift_tab_page.dart | 217 ++++- .../popular/follow/sc_room_follow_page.dart | 583 +++++------ .../popular/history/sc_room_history_page.dart | 583 +++++------ .../home/popular/mine/sc_home_mine_page.dart | 903 ++++++------------ .../popular/mine/sc_home_mine_skeleton.dart | 293 ++++++ .../popular/party/sc_home_party_page.dart | 399 +++++++- lib/modules/index/index_page.dart | 43 +- lib/modules/search/sc_search_page.dart | 32 +- .../store/chatbox/store_chatbox_page.dart | 483 +++++----- .../store/headdress/store_headdress_page.dart | 483 +++++----- .../store/mountains/store_mountains_page.dart | 420 ++++---- lib/modules/store/theme/store_theme_page.dart | 472 +++++---- .../my_items/chatbox/bags_chatbox_page.dart | 766 +++++++-------- .../headdress/bags_headdress_page.dart | 35 +- .../mountains/bags_mountains_page.dart | 736 +++++++------- .../user/my_items/theme/bags_theme_page.dart | 723 +++++++------- .../general/sc_app_general_manager.dart | 180 +++- lib/services/room/rc_room_manager.dart | 26 +- lib/shared/tools/sc_network_image_utils.dart | 85 ++ lib/ui_kit/components/sc_compontent.dart | 46 +- lib/ui_kit/components/sc_float_ichart.dart | 7 +- .../room/room_live_audio_indicator.dart | 137 +++ .../widgets/room/room_online_user_widget.dart | 5 + .../widgets/store/store_bag_page_helpers.dart | 288 ++++++ 需求进度.md | 26 + 25 files changed, 4552 insertions(+), 3419 deletions(-) create mode 100644 lib/modules/home/popular/mine/sc_home_mine_skeleton.dart create mode 100644 lib/shared/tools/sc_network_image_utils.dart create mode 100644 lib/ui_kit/widgets/room/room_live_audio_indicator.dart create mode 100644 lib/ui_kit/widgets/store/store_bag_page_helpers.dart diff --git a/lib/modules/gift/gift_tab_page.dart b/lib/modules/gift/gift_tab_page.dart index 8d9af7d..1a36d57 100644 --- a/lib/modules/gift/gift_tab_page.dart +++ b/lib/modules/gift/gift_tab_page.dart @@ -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 { - PageController _giftPageController = PageController(); +class _GiftTabPageState extends State + with AutomaticKeepAliveClientMixin { + final PageController _giftPageController = PageController(); + final Map _pageStatuses = + {}; 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 { @override Widget build(BuildContext context) { + super.build(context); return Consumer( builder: (context, ref, child) { - return (ref.giftByTab[widget.type] ?? []).isEmpty - ? mainEmpty( - textColor: Colors.white54, - msg: SCAppLocalizations.of(context)!.noData, - ) + final gifts = ref.giftByTab[widget.type] ?? []; + 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 { 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 { ), 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 { ); } + void _schedulePageWarmup( + SCAppGeneralManager ref, + List 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([ + Future.wait([ + warmupTask, + Future.delayed(_kGiftPageSkeletonMinDuration), + ]), + Future.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 { fit: BoxFit.cover, width: 48.w, height: 48.w, + loadingWidget: _buildGiftCoverLoading(), + errorWidget: _buildGiftCoverLoading(), ), SizedBox(height: 5.w), Container( diff --git a/lib/modules/home/popular/follow/sc_room_follow_page.dart b/lib/modules/home/popular/follow/sc_room_follow_page.dart index bbacb1c..0c9a3a7 100644 --- a/lib/modules/home/popular/follow/sc_room_follow_page.dart +++ b/lib/modules/home/popular/follow/sc_room_follow_page.dart @@ -1,285 +1,298 @@ -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: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/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'; - -///关注房间 -class SCRoomFollowPage extends SCPageList { - @override - _RoomFollowPageState createState() => _RoomFollowPageState(); -} - -class _RoomFollowPageState - extends SCPageListState { - String? lastId; - bool _isLoading = true; // 添加加载状态 - - @override - void initState() { - super.initState(); - enablePullUp = true; - backgroundColor = Colors.transparent; - isGridView = true; - gridViewCount = 2; - loadData(1); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - backgroundColor: Colors.transparent, - body: buildList(context), - ); - } - - @override - Widget buildItem(FollowRoomRes roomRes) { - return GestureDetector( - 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( - roomRes.roomProfile?.id, - roomRes.roomProfile?.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( - builder: (_, provider, __) { - return netImage( - url: - "${provider.findCountryByName(roomRes.roomProfile?.countryName ?? "")?.nationalFlag}", - width: 20.w, - height: 13.w, - borderRadius: BorderRadius.circular(2.w), - ); - }, - ), - SizedBox(width: 5.w), - Expanded( - child: SizedBox( - height: 21.w, - child: text( - roomRes.roomProfile?.roomName ?? "", - fontSize: 15.sp, - textColor: Color(0xffffffff), - fontWeight: FontWeight.w400, - ), - // (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), - (roomRes - .roomProfile - ?.extValues - ?.roomSetting - ?.password - ?.isEmpty ?? - false) - ? Image.asset( - "sc_images/general/sc_icon_online_user.png", - width: 14.w, - height: 14.w, - ) - : Image.asset( - "sc_images/index/sc_icon_room_suo.png", - width: 20.w, - height: 20.w, - ), - (roomRes - .roomProfile - ?.extValues - ?.roomSetting - ?.password - ?.isEmpty ?? - false) - ? SizedBox(width: 3.w) - : Container(height: 10.w), - (roomRes - .roomProfile - ?.extValues - ?.roomSetting - ?.password - ?.isEmpty ?? - false) - ? text( - roomRes.roomProfile?.extValues?.memberQuantity ?? "0", - fontSize: 12.sp, - ) - : Container(height: 10.w), - SizedBox(width: 10.w), - ], - ), - ), - getRoomCoverHeaddress(roomRes).isNotEmpty - ? Transform.translate( - offset: Offset(0, -5.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: 25.w, - height: 25.w, - borderRadius: BorderRadius.circular(4.w), - ), - ) - : Container(), - ], - ), - ), - onTap: () { - Provider.of( - context, - listen: false, - ).joinVoiceRoomSession(context, roomRes.roomProfile?.id ?? ""); - }, - ); - } - - String getRoomCoverHeaddress(FollowRoomRes roomRes) { - return ""; - } - - @override - empty() { - List list = []; - list.add(SizedBox(height: height(30))); - list.add( - Image.asset( - 'sc_images/general/sc_icon_loading.png', - width: 120.w, - height: 120.w, - ), - ); - list.add(SizedBox(height: height(15))); - list.add( - Text( - SCAppLocalizations.of(context)!.youHaventFollowed, - style: TextStyle( - fontSize: sp(14), - color: Color(0xff999999), - fontWeight: FontWeight.w400, - decoration: TextDecoration.none, - height: 1, - ), - ), - ); - return Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: list, - ); - } - - ///加载数据 - @override - loadPage({ - required int page, - required Function(List) onSuccess, - Function? onErr, - }) async { - 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(); - } - } finally { - // 无论成功失败,都隐藏骨架屏 - setState(() { - _isLoading = false; - }); - } - } -} +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:yumi/app_localizations.dart'; +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 + SCPageListState createState() => _RoomFollowPageState(); +} + +class _RoomFollowPageState + extends SCPageListState { + String? lastId; + + @override + void initState() { + super.initState(); + enablePullUp = true; + backgroundColor = Colors.transparent; + isGridView = true; + gridViewCount = 2; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + 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( + 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( + roomRes.roomProfile?.id, + roomRes.roomProfile?.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( + builder: (_, provider, __) { + return netImage( + url: + "${provider.findCountryByName(roomRes.roomProfile?.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( + roomRes.roomProfile?.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), + (roomRes + .roomProfile + ?.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, + ), + (roomRes + .roomProfile + ?.extValues + ?.roomSetting + ?.password + ?.isEmpty ?? + false) + ? SizedBox(width: 3.w) + : Container(height: 10.w), + (roomRes + .roomProfile + ?.extValues + ?.roomSetting + ?.password + ?.isEmpty ?? + false) + ? text( + roomRes.roomProfile?.extValues?.memberQuantity ?? "0", + fontSize: 10.sp, + lineHeight: 1, + ) + : Container(height: 10.w), + SizedBox(width: 10.w), + ], + ), + ), + getRoomCoverHeaddress(roomRes).isNotEmpty + ? Transform.translate( + offset: Offset(0, -5.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: 25.w, + height: 25.w, + borderRadius: BorderRadius.circular(4.w), + ), + ) + : Container(), + ], + ), + ), + onTap: () { + Provider.of( + context, + listen: false, + ).joinVoiceRoomSession(context, roomRes.roomProfile?.id ?? ""); + }, + ); + } + + String getRoomCoverHeaddress(FollowRoomRes roomRes) { + return ""; + } + + @override + empty() { + List list = []; + list.add(SizedBox(height: height(30))); + list.add( + Image.asset( + 'sc_images/general/sc_icon_loading.png', + width: 120.w, + height: 120.w, + ), + ); + list.add(SizedBox(height: height(15))); + list.add( + Text( + SCAppLocalizations.of(context)!.youHaventFollowed, + style: TextStyle( + fontSize: sp(14), + color: Color(0xff999999), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ); + return Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: list, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + 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(); + } + } + } +} diff --git a/lib/modules/home/popular/history/sc_room_history_page.dart b/lib/modules/home/popular/history/sc_room_history_page.dart index abb29b6..ce86049 100644 --- a/lib/modules/home/popular/history/sc_room_history_page.dart +++ b/lib/modules/home/popular/history/sc_room_history_page.dart @@ -1,285 +1,298 @@ -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/repositories/sc_user_repository_impl.dart'; -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/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'; - -///历史房间 -class SCRoomHistoryPage extends SCPageList { - @override - _SCRoomHistoryPageState createState() => _SCRoomHistoryPageState(); -} - -class _SCRoomHistoryPageState - extends SCPageListState { - String? lastId; - bool _isLoading = true; // 添加加载状态 - - @override - void initState() { - super.initState(); - enablePullUp = true; - backgroundColor = Colors.transparent; - isGridView = true; - gridViewCount = 2; - loadData(1); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - backgroundColor: Colors.transparent, - body: buildList(context), - ); - } - - @override - Widget buildItem(FollowRoomRes roomRes) { - return GestureDetector( - 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( - roomRes.roomProfile?.id, - roomRes.roomProfile?.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( - builder: (_, provider, __) { - return netImage( - url: - "${provider.findCountryByName(roomRes.roomProfile?.countryName ?? "")?.nationalFlag}", - width: 20.w, - height: 13.w, - borderRadius: BorderRadius.circular(2.w), - ); - }, - ), - SizedBox(width: 5.w), - Expanded( - child: SizedBox( - height: 21.w, - child: text( - roomRes.roomProfile?.roomName ?? "", - fontSize: 15.sp, - textColor: Color(0xffffffff), - fontWeight: FontWeight.w400, - ), - - // (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), - (roomRes - .roomProfile - ?.extValues - ?.roomSetting - ?.password - ?.isEmpty ?? - false) - ? Image.asset( - "sc_images/general/sc_icon_online_user.png", - width: 14.w, - height: 14.w, - ) - : Image.asset( - "sc_images/index/sc_icon_room_suo.png", - width: 20.w, - height: 20.w, - ), - (roomRes - .roomProfile - ?.extValues - ?.roomSetting - ?.password - ?.isEmpty ?? - false) - ? SizedBox(width: 3.w) - : Container(height: 10.w), - (roomRes - .roomProfile - ?.extValues - ?.roomSetting - ?.password - ?.isEmpty ?? - false) - ? text( - roomRes.roomProfile?.extValues?.memberQuantity ?? "0", - fontSize: 12.sp, - ) - : Container(height: 10.w), - SizedBox(width: 10.w), - ], - ), - ), - getRoomCoverHeaddress(roomRes).isNotEmpty - ? Transform.translate( - offset: Offset(0, -5.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: 25.w, - height: 25.w, - borderRadius: BorderRadius.circular(4.w), - ), - ) - : Container(), - ], - ), - ), - onTap: () { - Provider.of( - context, - listen: false, - ).joinVoiceRoomSession(context, roomRes.roomProfile?.id ?? ""); - }, - ); - } - - @override - empty() { - List list = []; - list.add(SizedBox(height: height(30))); - list.add( - Image.asset( - 'sc_images/general/sc_icon_loading.png', - width: 120.w, - height: 120.w, - ), - ); - list.add(SizedBox(height: height(15))); - list.add( - Text( - SCAppLocalizations.of(context)!.noData, - style: TextStyle( - fontSize: sp(14), - color: Color(0xff999999), - fontWeight: FontWeight.w400, - decoration: TextDecoration.none, - height: 1, - ), - ), - ); - return Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: list, - ); - } - - String getRoomCoverHeaddress(FollowRoomRes roomRes) { - return ""; - } - - ///加载数据 - @override - loadPage({ - required int page, - required Function(List) onSuccess, - Function? onErr, - }) async { - if (page == 1) { - lastId = null; - } - try { - var roomList = await SCAccountRepository().trace(lastId: lastId); - if (roomList.isNotEmpty) { - lastId = roomList.last.id; - } - onSuccess(roomList); - } catch (e) { - if (onErr != null) { - onErr(); - } - } finally { - // 无论成功失败,都隐藏骨架屏 - setState(() { - _isLoading = false; - }); - } - } -} +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/repositories/sc_user_repository_impl.dart'; +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 + SCPageListState createState() => _SCRoomHistoryPageState(); +} + +class _SCRoomHistoryPageState + extends SCPageListState { + String? lastId; + + @override + void initState() { + super.initState(); + enablePullUp = true; + backgroundColor = Colors.transparent; + isGridView = true; + gridViewCount = 2; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + 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( + 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( + roomRes.roomProfile?.id, + roomRes.roomProfile?.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( + builder: (_, provider, __) { + return netImage( + url: + "${provider.findCountryByName(roomRes.roomProfile?.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( + roomRes.roomProfile?.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), + (roomRes + .roomProfile + ?.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, + ), + (roomRes + .roomProfile + ?.extValues + ?.roomSetting + ?.password + ?.isEmpty ?? + false) + ? SizedBox(width: 3.w) + : Container(height: 10.w), + (roomRes + .roomProfile + ?.extValues + ?.roomSetting + ?.password + ?.isEmpty ?? + false) + ? text( + roomRes.roomProfile?.extValues?.memberQuantity ?? "0", + fontSize: 10.sp, + lineHeight: 1, + ) + : Container(height: 10.w), + SizedBox(width: 10.w), + ], + ), + ), + getRoomCoverHeaddress(roomRes).isNotEmpty + ? Transform.translate( + offset: Offset(0, -5.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: 25.w, + height: 25.w, + borderRadius: BorderRadius.circular(4.w), + ), + ) + : Container(), + ], + ), + ), + onTap: () { + Provider.of( + context, + listen: false, + ).joinVoiceRoomSession(context, roomRes.roomProfile?.id ?? ""); + }, + ); + } + + @override + empty() { + List list = []; + list.add(SizedBox(height: height(30))); + list.add( + Image.asset( + 'sc_images/general/sc_icon_loading.png', + width: 120.w, + height: 120.w, + ), + ); + list.add(SizedBox(height: height(15))); + list.add( + Text( + SCAppLocalizations.of(context)!.noData, + style: TextStyle( + fontSize: sp(14), + color: Color(0xff999999), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ); + return Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: list, + ); + } + + String getRoomCoverHeaddress(FollowRoomRes roomRes) { + return ""; + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (page == 1) { + lastId = null; + } + try { + var roomList = await SCAccountRepository().trace(lastId: lastId); + if (roomList.isNotEmpty) { + lastId = roomList.last.id; + } + onSuccess(roomList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/modules/home/popular/mine/sc_home_mine_page.dart b/lib/modules/home/popular/mine/sc_home_mine_page.dart index e0cf746..5e48918 100644 --- a/lib/modules/home/popular/mine/sc_home_mine_page.dart +++ b/lib/modules/home/popular/mine/sc_home_mine_page.dart @@ -1,607 +1,296 @@ -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 '../../../../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'; - -class SCHomeMinePage extends SCPageList { - @override - _HomeMinePageState createState() => _HomeMinePageState(); -} - -class _HomeMinePageState extends SCPageListState - with SingleTickerProviderStateMixin { - List historyRooms = []; - String? lastId; - - BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy; - late TabController _tabController; - final List _pages = [SCRoomHistoryPage(), SCRoomFollowPage()]; - bool isLoading = false; - final List _tabs = []; - - @override - void initState() { - super.initState(); - _tabController = TabController(length: 2, vsync: this); - enablePullUp = true; - backgroundColor = Colors.transparent; - loadData(1); - } - - @override - Widget build(BuildContext context) { - _tabs.clear(); - _tabs.add(Tab(text: SCAppLocalizations.of(context)!.recent)); - _tabs.add(Tab(text: SCAppLocalizations.of(context)!.followed)); - 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( - builder: (_, provider, __) { - return Container( - margin: EdgeInsets.symmetric(horizontal: 12.w), - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage( - provider.myRoom == null - ? "sc_images/index/sc_icon_my_room_no_bg.png" - : "sc_images/index/sc_icon_my_room_has_bg.png", - ), - fit: BoxFit.fill, - ), - ), - width: ScreenUtil().screenWidth, - height: 85.w, - child: _buildMyRoom(provider), - ); - }, - ), - SizedBox(height: 5.w), - Row( - children: [ - SizedBox(width: 5.w), - TabBar( - tabAlignment: TabAlignment.start, - labelPadding: EdgeInsets.symmetric(horizontal: 12.w), - labelColor: SocialChatTheme.primaryLight, - isScrollable: true, - indicator: BoxDecoration(), - unselectedLabelColor: Colors.white, - labelStyle: TextStyle( - fontSize: 15.sp, - fontFamily: 'MyCustomFont', - fontWeight: FontWeight.w600, - ), - unselectedLabelStyle: TextStyle( - fontSize: 13.sp, - fontFamily: 'MyCustomFont', - fontWeight: FontWeight.w500, - ), - indicatorColor: Colors.transparent, - dividerColor: Colors.transparent, - controller: _tabController, - tabs: _tabs, - ), - ], - ), - Expanded( - child: TabBarView( - controller: _tabController, - physics: NeverScrollableScrollPhysics(), - children: _pages, - ), - ), - ], - ); - } - - _buildMyRoom(SocialChatRoomManager provider) { - return provider.myRoom != null - ? GestureDetector( - behavior: HitTestBehavior.opaque, - child: Row( - children: [ - SizedBox(width: 10.w), - netImage( - url: resolveRoomCoverUrl( - provider.myRoom?.id, - provider.myRoom?.roomCover, - ), - defaultImg: kRoomCoverDefaultImg, - borderRadius: BorderRadius.all(Radius.circular(8.w)), - width: 70.w, - ), - SizedBox(width: 10.w), - Expanded( - child: Stack( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 6.w), - Row( - children: [ - netImage( - url: - Provider.of( - context, - listen: false, - ) - .findCountryByName( - AccountStorage() - .getCurrentUser() - ?.userProfile - ?.countryName ?? - "", - ) - ?.nationalFlag ?? - "", - borderRadius: BorderRadius.all( - Radius.circular(3.w), - ), - width: 19.w, - height: 14.w, - ), - SizedBox(width: 3.w), - Container( - constraints: BoxConstraints( - maxWidth: 200.w, - maxHeight: 24.w, - ), - child: - (provider.myRoom?.roomName?.length ?? 0) > 18 - ? Marquee( - text: provider.myRoom?.roomName ?? "", - style: TextStyle( - fontSize: 16.sp, - color: Colors.white, - fontWeight: FontWeight.bold, - decoration: TextDecoration.none, - ), - scrollAxis: Axis.horizontal, - crossAxisAlignment: - CrossAxisAlignment.start, - blankSpace: 40.0, - velocity: 40.0, - pauseAfterRound: Duration(seconds: 1), - accelerationDuration: Duration( - seconds: 1, - ), - accelerationCurve: Curves.easeOut, - decelerationDuration: Duration( - milliseconds: 500, - ), - decelerationCurve: Curves.easeOut, - ) - : Text( - provider.myRoom?.roomName ?? "", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 16.sp, - color: Colors.white, - fontWeight: FontWeight.bold, - decoration: TextDecoration.none, - ), - ), - ), - ], - ), - text( - provider.myRoom?.roomDesc ?? "", - fontSize: 14.sp, - textColor: Colors.white, - ), - text( - "ID:${provider.myRoom?.roomAccount}", - fontSize: 12.sp, - textColor: Colors.white, - ), - ], - ), - ], - ), - ), - SizedBox(width: 12.w), - ], - ), - onTap: () { - String roomId = - Provider.of( - context, - listen: false, - ).myRoom?.id ?? - ""; - Provider.of( - context, - listen: false, - ).joinVoiceRoomSession(context, roomId); - }, - ) - : GestureDetector( - child: Row( - children: [ - SizedBox(width: 10.w), - Image.asset( - "sc_images/index/sc_icon_index_creat_room_tag.png", - height: 70.w, - width: 70.w, - ), - SizedBox(width: 10.w), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - text( - SCAppLocalizations.of(context)!.crateMyRoom, - fontSize: 14.sp, - fontWeight: FontWeight.bold, - textColor: Colors.white, - ), - text( - SCAppLocalizations.of(context)!.startYourBrandNewJourney, - fontSize: 12.sp, - textColor: Colors.white, - ), - ], - ), - ), - Image.asset( - "sc_images/index/sc_icon_my_room_tag2.png", - height: 25.w, - width: 25.w, - ), - SizedBox(width: 10.w), - Icon( - Icons.chevron_right_outlined, - color: Colors.white, - size: 20.w, - ), - SizedBox(width: 15.w), - ], - ), - onTap: () { - provider.createNewRoom(context); - }, - ); - } - - void _loadOtherData() { - SCLoadingManager.show(); - Provider.of( - 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( - 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( - 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( - 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) 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 ""; - } -} +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/ui_kit/components/text/sc_text.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import '../../../../services/general/sc_app_general_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}); + + @override + State createState() => _HomeMinePageState(); +} + +class _HomeMinePageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = const [SCRoomHistoryPage(), SCRoomFollowPage()]; + final List _tabs = []; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 2, vsync: this); + Provider.of( + context, + listen: false, + ).fetchMyRoomData(); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(Tab(text: SCAppLocalizations.of(context)!.recent)); + _tabs.add(Tab(text: SCAppLocalizations.of(context)!.followed)); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Consumer( + builder: (_, provider, __) { + if (provider.isMyRoomLoading && provider.myRoom == null) { + return const SCHomeSkeletonShimmer( + builder: _buildMineRoomSkeletonContent, + ); + } + return Container( + margin: EdgeInsets.symmetric(horizontal: 12.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + provider.myRoom == null + ? "sc_images/index/sc_icon_my_room_no_bg.png" + : "sc_images/index/sc_icon_my_room_has_bg.png", + ), + fit: BoxFit.fill, + ), + ), + width: ScreenUtil().screenWidth, + height: 85.w, + child: _buildMyRoom(provider), + ); + }, + ), + SizedBox(height: 5.w), + Row( + children: [ + SizedBox(width: 5.w), + TabBar( + tabAlignment: TabAlignment.start, + labelPadding: EdgeInsets.symmetric(horizontal: 12.w), + labelColor: SocialChatTheme.primaryLight, + isScrollable: true, + indicator: BoxDecoration(), + unselectedLabelColor: Colors.white, + labelStyle: TextStyle( + fontSize: 15.sp, + fontFamily: 'MyCustomFont', + fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: TextStyle( + fontSize: 13.sp, + fontFamily: 'MyCustomFont', + fontWeight: FontWeight.w500, + ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + ], + ), + Expanded( + child: TabBarView( + controller: _tabController, + physics: NeverScrollableScrollPhysics(), + children: _pages, + ), + ), + ], + ); + } + + static Widget _buildMineRoomSkeletonContent( + BuildContext context, + double progress, + ) { + return buildHomeMyRoomSkeleton(progress); + } + + Widget _buildMyRoom(SocialChatRoomManager provider) { + return provider.myRoom != null + ? GestureDetector( + behavior: HitTestBehavior.opaque, + child: Row( + children: [ + SizedBox(width: 10.w), + netImage( + url: resolveRoomCoverUrl( + provider.myRoom?.id, + provider.myRoom?.roomCover, + ), + defaultImg: kRoomCoverDefaultImg, + borderRadius: BorderRadius.all(Radius.circular(8.w)), + width: 70.w, + ), + SizedBox(width: 10.w), + Expanded( + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 6.w), + Row( + children: [ + netImage( + url: + Provider.of( + context, + listen: false, + ) + .findCountryByName( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.countryName ?? + "", + ) + ?.nationalFlag ?? + "", + borderRadius: BorderRadius.all( + Radius.circular(3.w), + ), + width: 19.w, + height: 14.w, + ), + SizedBox(width: 3.w), + Container( + constraints: BoxConstraints( + maxWidth: 200.w, + maxHeight: 24.w, + ), + child: + (provider.myRoom?.roomName?.length ?? 0) > 18 + ? Marquee( + text: provider.myRoom?.roomName ?? "", + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + provider.myRoom?.roomName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + text( + provider.myRoom?.roomDesc ?? "", + fontSize: 14.sp, + textColor: Colors.white, + ), + text( + "ID:${provider.myRoom?.roomAccount}", + fontSize: 12.sp, + textColor: Colors.white, + ), + ], + ), + ], + ), + ), + SizedBox(width: 12.w), + ], + ), + onTap: () { + String roomId = + Provider.of( + context, + listen: false, + ).myRoom?.id ?? + ""; + Provider.of( + context, + listen: false, + ).joinVoiceRoomSession(context, roomId); + }, + ) + : GestureDetector( + child: Row( + children: [ + SizedBox(width: 10.w), + Image.asset( + "sc_images/index/sc_icon_index_creat_room_tag.png", + height: 70.w, + width: 70.w, + ), + SizedBox(width: 10.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + SCAppLocalizations.of(context)!.crateMyRoom, + fontSize: 14.sp, + fontWeight: FontWeight.bold, + textColor: Colors.white, + ), + text( + SCAppLocalizations.of(context)!.startYourBrandNewJourney, + fontSize: 12.sp, + textColor: Colors.white, + ), + ], + ), + ), + Image.asset( + "sc_images/index/sc_icon_my_room_tag2.png", + height: 25.w, + width: 25.w, + ), + SizedBox(width: 10.w), + Icon( + Icons.chevron_right_outlined, + color: Colors.white, + size: 20.w, + ), + SizedBox(width: 15.w), + ], + ), + onTap: () { + provider.createNewRoom(context); + }, + ); + } +} diff --git a/lib/modules/home/popular/mine/sc_home_mine_skeleton.dart b/lib/modules/home/popular/mine/sc_home_mine_skeleton.dart new file mode 100644 index 0000000..3053f6b --- /dev/null +++ b/lib/modules/home/popular/mine/sc_home_mine_skeleton.dart @@ -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 createState() => _SCHomeSkeletonShimmerState(); +} + +class _SCHomeSkeletonShimmerState extends State + 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), + ), + ], + ), + ), + ), + ], + ), + ), + ); +} diff --git a/lib/modules/home/popular/party/sc_home_party_page.dart b/lib/modules/home/popular/party/sc_home_party_page.dart index a94590e..27ac498 100644 --- a/lib/modules/home/popular/party/sc_home_party_page.dart +++ b/lib/modules/home/popular/party/sc_home_party_page.dart @@ -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 createState() => _HomePartyPageState(); } class _HomePartyPageState extends State @@ -29,18 +37,31 @@ class _HomePartyPageState extends State List historyRooms = []; String? lastId; bool isLoading = false; + bool _isLeaderboardLoading = false; final RefreshController _refreshController = RefreshController( initialRefresh: false, ); + late final AnimationController _skeletonController; List 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 ), Consumer( 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 borderRadius: BorderRadius.circular(12.w), ), onTap: () { - print('ads:${item.toJson()}'); SCBannerUtils.openBanner(item, context); }, ); @@ -384,9 +376,303 @@ class _HomePartyPageState extends State ); } + 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( + context, + listen: false, + ); setState(() { isLoading = true; + _isLeaderboardLoading = generalManager.appLeaderResult == null; }); SCChatRoomRepository() .discovery(allRegion: true) @@ -403,8 +689,15 @@ class _HomePartyPageState extends State isLoading = false; if (mounted) setState(() {}); }); - Provider.of(context, listen: false).loadMainBanner(); - Provider.of(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 SizedBox(width: 5.w), Expanded( child: SizedBox( - height: 21.w, - child: text( - res.roomName ?? "", - fontSize: 15.sp, - textColor: Color(0xffffffff), - fontWeight: FontWeight.w400, + 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( @@ -505,11 +805,7 @@ class _HomePartyPageState extends State ), 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 (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), diff --git a/lib/modules/index/index_page.dart b/lib/modules/index/index_page.dart index 1956ea9..3d4fc56 100644 --- a/lib/modules/index/index_page.dart +++ b/lib/modules/index/index_page.dart @@ -96,25 +96,34 @@ class _SCIndexPageState extends State { fit: BoxFit.fill, ), ), - child: BottomNavigationBar( - elevation: 0, - backgroundColor: Colors.transparent, - selectedLabelStyle: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 14.sp, + child: Theme( + data: Theme.of(context).copyWith( + splashFactory: NoSplash.splashFactory, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + hoverColor: Colors.transparent, ), - unselectedLabelStyle: TextStyle( - fontWeight: FontWeight.w500, - fontSize: 13.sp, + child: BottomNavigationBar( + elevation: 0, + enableFeedback: false, + backgroundColor: Colors.transparent, + selectedLabelStyle: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + unselectedLabelStyle: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 13.sp, + ), + type: BottomNavigationBarType.fixed, + selectedItemColor: Color(0xffBF854A), + unselectedItemColor: Color(0xffC4C4C4), + showUnselectedLabels: true, + showSelectedLabels: true, + items: _bottomItems, + currentIndex: _currentIndex, + onTap: (index) => setState(() => _currentIndex = index), ), - type: BottomNavigationBarType.fixed, - selectedItemColor: Color(0xffBF854A), - unselectedItemColor: Color(0xffC4C4C4), - showUnselectedLabels: true, - showSelectedLabels: true, - items: _bottomItems, - currentIndex: _currentIndex, - onTap: (index) => setState(() => _currentIndex = index), ), ), ), diff --git a/lib/modules/search/sc_search_page.dart b/lib/modules/search/sc_search_page.dart index f507b07..0c1e82d 100644 --- a/lib/modules/search/sc_search_page.dart +++ b/lib/modules/search/sc_search_page.dart @@ -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 { SizedBox(width: 5.w), Expanded( child: SizedBox( - height: 21.w, - child: text( - e.roomName ?? "", - fontSize: 15.sp, - textColor: Color(0xffffffff), - fontWeight: FontWeight.w400, + height: 17.w, + child: Align( + alignment: Alignment.centerLeft, + child: Transform.translate( + offset: Offset(0, -0.6.w), + child: text( + e.roomName ?? "", + 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 { ), 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 { ? 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), ], diff --git a/lib/modules/store/chatbox/store_chatbox_page.dart b/lib/modules/store/chatbox/store_chatbox_page.dart index 5890dc1..880bb32 100644 --- a/lib/modules/store/chatbox/store_chatbox_page.dart +++ b/lib/modules/store/chatbox/store_chatbox_page.dart @@ -1,243 +1,240 @@ -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'; - -import 'package:yumi/ui_kit/components/sc_page_list.dart'; -import 'package:yumi/shared/tools/sc_loading_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_chatbox_detail_dialog.dart'; - -import '../../../shared/data_sources/models/enum/sc_currency_type.dart'; -import '../../../shared/data_sources/models/enum/sc_props_type.dart'; - -///聊天气泡框 -class StoreChatboxPage extends SCPageList { - @override - _StoreChatboxPageState createState() => _StoreChatboxPageState(); -} - -class _StoreChatboxPageState - extends SCPageListState { - //折扣 - double disCount = 1; - - @override - void initState() { - super.initState(); - enablePullUp = false; - isGridView = true; - gridViewCount = 3; - padding = EdgeInsets.symmetric(horizontal: 6.w); - backgroundColor = Colors.transparent; - gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: gridViewCount, - mainAxisSpacing: 12.w, - crossAxisSpacing: 12.w, - childAspectRatio: 0.78, - ); - loadData(1); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.transparent, - body: buildList(context), - ); - } - - @override - Widget buildItem(StoreListResBean res) { - return GestureDetector( - child: Container( - decoration: BoxDecoration( - color: - res.isSelecte - ? Color(0xff18F2B1).withOpacity(0.1) - : SCGlobalConfig.businessLogicStrategy - .getStoreItemBackgroundColor(), - border: Border.all( - color: - res.isSelecte - ? SCGlobalConfig.businessLogicStrategy - .getStoreItemSelectedBorderColor() - : SCGlobalConfig.businessLogicStrategy - .getStoreItemUnselectedBorderColor(), - width: 1.w, - ), - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - child: Stack( - children: [ - Column( - children: [ - SizedBox(height: 10.w), - netImage( - url: res.res.propsResources?.cover ?? "", - height: 45.w, - fit: BoxFit.contain, - ), - SizedBox(height: 3.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - text( - res.res.propsResources?.name ?? "", - textColor: Colors.white, - fontSize: 11.sp, - ), - ], - ), - SizedBox(height: 3.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 10.w), - Expanded( - child: Text.rich( - textAlign: TextAlign.center, - TextSpan( - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Image.asset( - SCGlobalConfig.businessLogicStrategy - .getStoreItemGoldIcon(), - width: 22.w, - ), - ), - TextSpan(text: " "), - disCount < 1 - ? TextSpan( - text: "${res.res.propsPrices![0].amount}", - style: TextStyle( - fontSize: 12.sp, - color: - SCGlobalConfig.businessLogicStrategy - .getStoreItemPriceTextColor(), - fontWeight: FontWeight.w600, - decoration: TextDecoration.lineThrough, - ), - ) - : TextSpan(), - disCount < 1 ? TextSpan(text: " ") : TextSpan(), - TextSpan( - text: - "${((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(), - fontWeight: FontWeight.w600, - ), - ), - ], - ), - strutStyle: StrutStyle( - height: 1.3, // 行高倍数 - fontWeight: FontWeight.w500, - forceStrutHeight: true, // 强制应用行高 - ), - ), - ), - SizedBox(width: 10.w), - ], - ), - ], - ), - ], - ), - ), - onTap: () { - _selectItem(res); - _showDetail(res.res); - }, - ); - } - - ///加载数据 - @override - loadPage({ - required int page, - required Function(List) onSuccess, - Function? onErr, - }) async { - try { - List beans = []; - var storeList = await SCStoreRepositoryImp().storeList( - SCCurrencyType.GOLD.name, - SCPropsType.CHAT_BUBBLE.name, - ); - for (var value in storeList) { - beans.add(StoreListResBean(value, false)); - } - onSuccess(beans); - } catch (e) { - if (onErr != null) { - onErr(); - } - } - } - - void _showDetail(StoreListRes res) { - SmartDialog.show( - tag: "showPropsDetail", - alignment: Alignment.bottomCenter, - animationType: SmartAnimationType.fade, - builder: (_) { - return PropsStoreChatboxDetailDialog(res,disCount,); - }, - ); - } - - void _selectItem(StoreListResBean res) { - for (var value in items) { - value.isSelecte = false; - } - res.isSelecte = true; - setState(() {}); - } - - void _buy(StoreListResBean res) { - if (res.res.propsPrices!.isEmpty) { - return; - } - SCLoadingManager.show(); - num days = res.res.propsPrices![0].days ?? 0; - SCStoreRepositoryImp() - .storePurchasing( - res.res.id ?? "", - SCPropsType.CHAT_BUBBLE.name, - SCCurrencyType.GOLD.name, - "$days", - ) - .then((value) { - SCTts.show(SCAppLocalizations.of(context)!.purchaseIsSuccessful); - Provider.of( - context, - listen: false, - ).updateBalance(value); - SCLoadingManager.hide(); - }) - .catchError((e) { - SCLoadingManager.hide(); - }); - } -} - -class StoreListResBean { - StoreListRes res; - bool isSelecte = false; - - StoreListResBean(this.res, this.isSelecte); -} +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/sc_tts.dart'; +import 'package:yumi/app/constants/sc_global_config.dart'; +import 'package:provider/provider.dart'; + +import 'package:yumi/ui_kit/components/sc_page_list.dart'; +import 'package:yumi/shared/tools/sc_loading_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_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'; + +///聊天气泡框 +class StoreChatboxPage extends SCPageList { + @override + _StoreChatboxPageState createState() => _StoreChatboxPageState(); +} + +class _StoreChatboxPageState + extends SCPageListState { + //折扣 + double disCount = 1; + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.78, + ); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: + items.isEmpty && isLoading + ? const SCStoreGridSkeleton() + : buildList(context), + ); + } + + @override + Widget buildItem(StoreListResBean res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: + res.isSelecte + ? const Color(0xff18F2B1).withValues(alpha: 0.1) + : SCGlobalConfig.businessLogicStrategy + .getStoreItemBackgroundColor(), + border: Border.all( + color: + res.isSelecte + ? SCGlobalConfig.businessLogicStrategy + .getStoreItemSelectedBorderColor() + : SCGlobalConfig.businessLogicStrategy + .getStoreItemUnselectedBorderColor(), + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + children: [ + Column( + children: [ + SizedBox(height: 10.w), + netImage( + url: res.res.propsResources?.cover ?? "", + height: 45.w, + fit: BoxFit.contain, + ), + SizedBox(height: 3.w), + buildStoreBagItemTitle( + res.res.propsResources?.name ?? "", + textColor: Colors.white, + fontSize: 11.sp, + ), + SizedBox(height: 3.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 10.w), + Expanded( + child: Text.rich( + textAlign: TextAlign.center, + TextSpan( + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Image.asset( + SCGlobalConfig.businessLogicStrategy + .getStoreItemGoldIcon(), + width: 22.w, + ), + ), + TextSpan(text: " "), + disCount < 1 + ? TextSpan( + text: "${res.res.propsPrices![0].amount}", + style: TextStyle( + fontSize: 12.sp, + color: + SCGlobalConfig.businessLogicStrategy + .getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ), + ) + : TextSpan(), + disCount < 1 ? TextSpan(text: " ") : TextSpan(), + TextSpan( + text: + "${((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(), + fontWeight: FontWeight.w600, + ), + ), + ], + ), + strutStyle: StrutStyle( + height: 1.3, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + ), + ), + SizedBox(width: 10.w), + ], + ), + ], + ), + ], + ), + ), + onTap: () { + _selectItem(res); + _showDetail(res.res); + }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + List beans = []; + var storeList = await SCStoreRepositoryImp().storeList( + SCCurrencyType.GOLD.name, + SCPropsType.CHAT_BUBBLE.name, + ); + for (var value in storeList) { + beans.add(StoreListResBean(value, false)); + } + onSuccess(beans); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _showDetail(StoreListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsStoreChatboxDetailDialog(res, disCount); + }, + ); + } + + void _selectItem(StoreListResBean res) { + for (var value in items) { + value.isSelecte = false; + } + res.isSelecte = true; + setState(() {}); + } + + void _buy(StoreListResBean res) { + if (res.res.propsPrices!.isEmpty) { + return; + } + SCLoadingManager.show(); + num days = res.res.propsPrices![0].days ?? 0; + SCStoreRepositoryImp() + .storePurchasing( + res.res.id ?? "", + SCPropsType.CHAT_BUBBLE.name, + SCCurrencyType.GOLD.name, + "$days", + ) + .then((value) { + SCTts.show(SCAppLocalizations.of(context)!.purchaseIsSuccessful); + Provider.of( + context, + listen: false, + ).updateBalance(value); + SCLoadingManager.hide(); + }) + .catchError((e) { + SCLoadingManager.hide(); + }); + } +} + +class StoreListResBean { + StoreListRes res; + bool isSelecte = false; + + StoreListResBean(this.res, this.isSelecte); +} diff --git a/lib/modules/store/headdress/store_headdress_page.dart b/lib/modules/store/headdress/store_headdress_page.dart index ec3af35..b4e8edf 100644 --- a/lib/modules/store/headdress/store_headdress_page.dart +++ b/lib/modules/store/headdress/store_headdress_page.dart @@ -1,244 +1,239 @@ -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'; -import 'package:provider/provider.dart'; -import 'package:yumi/ui_kit/components/sc_page_list.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_headdress_detail_dialog.dart'; - -import '../../../shared/data_sources/models/enum/sc_currency_type.dart'; -import '../../../shared/data_sources/models/enum/sc_props_type.dart'; - -///头饰 -class StoreHeaddressPage extends SCPageList { - @override - _StoreHeaddressPageState createState() => _StoreHeaddressPageState(); -} - -class _StoreHeaddressPageState - extends SCPageListState { - //折扣 - double disCount = 1; - - @override - void initState() { - super.initState(); - enablePullUp = false; - isGridView = true; - gridViewCount = 3; - padding = EdgeInsets.symmetric(horizontal: 6.w); - backgroundColor = Colors.transparent; - gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: gridViewCount, - mainAxisSpacing: 12.w, - crossAxisSpacing: 12.w, - childAspectRatio: 0.78, - ); - loadData(1); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.transparent, - body: buildList(context), - ); - } - - @override - Widget buildItem(StoreListResBean res) { - return GestureDetector( - child: Container( - decoration: BoxDecoration( - color: - res.isSelecte - ? Color(0xff18F2B1).withOpacity(0.1) - : SCGlobalConfig.businessLogicStrategy - .getStoreItemBackgroundColor(), - border: Border.all( - color: - res.isSelecte - ? SCGlobalConfig.businessLogicStrategy - .getStoreItemSelectedBorderColor() - : SCGlobalConfig.businessLogicStrategy - .getStoreItemUnselectedBorderColor(), - width: 1.w, - ), - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - child: Stack( - children: [ - Column( - children: [ - SizedBox(height: 10.w), - netImage( - url: res.res.propsResources?.cover ?? "", - width: 55.w, - height: 55.w, - ), - SizedBox(height: 3.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - text( - res.res.propsResources?.name ?? "", - textColor: Colors.white, - fontSize: 11.sp, - ), - ], - ), - SizedBox(height: 3.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 10.w), - Expanded( - child: Text.rich( - textAlign: TextAlign.center, - TextSpan( - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Image.asset( - SCGlobalConfig.businessLogicStrategy - .getStoreItemGoldIcon(), - width: 22.w, - ), - ), - TextSpan(text: " "), - disCount < 1 - ? TextSpan( - text: "${res.res.propsPrices![0].amount}", - style: TextStyle( - fontSize: 12.sp, - color: - SCGlobalConfig.businessLogicStrategy - .getStoreItemPriceTextColor(), - fontWeight: FontWeight.w600, - decoration: TextDecoration.lineThrough, - ), - ) - : TextSpan(), - disCount < 1 ? TextSpan(text: " ") : TextSpan(), - TextSpan( - text: - "${((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(), - fontWeight: FontWeight.w600, - ), - ), - ], - ), - strutStyle: StrutStyle( - height: 1.3, // 行高倍数 - fontWeight: FontWeight.w500, - forceStrutHeight: true, // 强制应用行高 - ), - ), - ), - SizedBox(width: 10.w), - ], - ), - ], - ), - ], - ), - ), - onTap: () { - _selectItem(res); - _showDetail(res.res); - - }, - ); - } - - ///加载数据 - @override - loadPage({ - required int page, - required Function(List) onSuccess, - Function? onErr, - }) async { - try { - List beans = []; - var storeList = await SCStoreRepositoryImp().storeList( - SCCurrencyType.GOLD.name, - SCPropsType.AVATAR_FRAME.name, - ); - for (var value in storeList) { - beans.add(StoreListResBean(value, false)); - } - onSuccess(beans); - } catch (e) { - if (onErr != null) { - onErr(); - } - } - } - - void _showDetail(StoreListRes res) { - SmartDialog.show( - tag: "showPropsDetail", - alignment: Alignment.bottomCenter, - animationType: SmartAnimationType.fade, - builder: (_) { - return PropsStoreHeaddressDetailDialog(res, disCount); - }, - ); - } - - void _selectItem(StoreListResBean res) { - for (var value in items) { - value.isSelecte = false; - } - res.isSelecte = true; - setState(() {}); - } - - void _buy(StoreListResBean res) { - if (res.res.propsPrices!.isEmpty) { - return; - } - SCLoadingManager.show(); - num days = res.res.propsPrices![0].days ?? 0; - SCStoreRepositoryImp() - .storePurchasing( - res.res.id ?? "", - SCPropsType.AVATAR_FRAME.name, - SCCurrencyType.GOLD.name, - "$days", - ) - .then((value) { - SCTts.show(SCAppLocalizations.of(context)!.purchaseIsSuccessful); - Provider.of( - context, - listen: false, - ).updateBalance(value); - SCLoadingManager.hide(); - }) - .catchError((e) { - SCLoadingManager.hide(); - }); - } -} - -class StoreListResBean { - StoreListRes res; - bool isSelecte = false; - - StoreListResBean(this.res, this.isSelecte); -} +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/sc_tts.dart'; +import 'package:yumi/app/constants/sc_global_config.dart'; +import 'package:yumi/shared/tools/sc_loading_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:yumi/ui_kit/components/sc_page_list.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_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'; + +///头饰 +class StoreHeaddressPage extends SCPageList { + @override + _StoreHeaddressPageState createState() => _StoreHeaddressPageState(); +} + +class _StoreHeaddressPageState + extends SCPageListState { + //折扣 + double disCount = 1; + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.78, + ); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: + items.isEmpty && isLoading + ? const SCStoreGridSkeleton() + : buildList(context), + ); + } + + @override + Widget buildItem(StoreListResBean res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: + res.isSelecte + ? const Color(0xff18F2B1).withValues(alpha: 0.1) + : SCGlobalConfig.businessLogicStrategy + .getStoreItemBackgroundColor(), + border: Border.all( + color: + res.isSelecte + ? SCGlobalConfig.businessLogicStrategy + .getStoreItemSelectedBorderColor() + : SCGlobalConfig.businessLogicStrategy + .getStoreItemUnselectedBorderColor(), + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + children: [ + Column( + children: [ + SizedBox(height: 10.w), + netImage( + url: res.res.propsResources?.cover ?? "", + width: 55.w, + height: 55.w, + ), + SizedBox(height: 3.w), + buildStoreBagItemTitle( + res.res.propsResources?.name ?? "", + textColor: Colors.white, + fontSize: 11.sp, + ), + SizedBox(height: 3.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 10.w), + Expanded( + child: Text.rich( + textAlign: TextAlign.center, + TextSpan( + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Image.asset( + SCGlobalConfig.businessLogicStrategy + .getStoreItemGoldIcon(), + width: 22.w, + ), + ), + TextSpan(text: " "), + disCount < 1 + ? TextSpan( + text: "${res.res.propsPrices![0].amount}", + style: TextStyle( + fontSize: 12.sp, + color: + SCGlobalConfig.businessLogicStrategy + .getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ), + ) + : TextSpan(), + disCount < 1 ? TextSpan(text: " ") : TextSpan(), + TextSpan( + text: + "${((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(), + fontWeight: FontWeight.w600, + ), + ), + ], + ), + strutStyle: StrutStyle( + height: 1.3, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + ), + ), + SizedBox(width: 10.w), + ], + ), + ], + ), + ], + ), + ), + onTap: () { + _selectItem(res); + _showDetail(res.res); + }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + List beans = []; + var storeList = await SCStoreRepositoryImp().storeList( + SCCurrencyType.GOLD.name, + SCPropsType.AVATAR_FRAME.name, + ); + for (var value in storeList) { + beans.add(StoreListResBean(value, false)); + } + onSuccess(beans); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _showDetail(StoreListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsStoreHeaddressDetailDialog(res, disCount); + }, + ); + } + + void _selectItem(StoreListResBean res) { + for (var value in items) { + value.isSelecte = false; + } + res.isSelecte = true; + setState(() {}); + } + + void _buy(StoreListResBean res) { + if (res.res.propsPrices!.isEmpty) { + return; + } + SCLoadingManager.show(); + num days = res.res.propsPrices![0].days ?? 0; + SCStoreRepositoryImp() + .storePurchasing( + res.res.id ?? "", + SCPropsType.AVATAR_FRAME.name, + SCCurrencyType.GOLD.name, + "$days", + ) + .then((value) { + SCTts.show(SCAppLocalizations.of(context)!.purchaseIsSuccessful); + Provider.of( + context, + listen: false, + ).updateBalance(value); + SCLoadingManager.hide(); + }) + .catchError((e) { + SCLoadingManager.hide(); + }); + } +} + +class StoreListResBean { + StoreListRes res; + bool isSelecte = false; + + StoreListResBean(this.res, this.isSelecte); +} diff --git a/lib/modules/store/mountains/store_mountains_page.dart b/lib/modules/store/mountains/store_mountains_page.dart index 62d43d2..dd58d3a 100644 --- a/lib/modules/store/mountains/store_mountains_page.dart +++ b/lib/modules/store/mountains/store_mountains_page.dart @@ -1,210 +1,210 @@ -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/modules/store/headdress/store_headdress_page.dart'; - -import '../../../shared/data_sources/models/enum/sc_currency_type.dart'; -import '../../../shared/data_sources/models/enum/sc_props_type.dart'; - -///坐骑 -class StoreMountainsPage extends SCPageList { - StoreMountainsPage(); - - @override - _StoreMountainsPageState createState() => _StoreMountainsPageState(); -} - -class _StoreMountainsPageState - extends SCPageListState { - //折扣 - double disCount = 1; - - @override - void initState() { - super.initState(); - enablePullUp = false; - isGridView = true; - gridViewCount = 3; - padding = EdgeInsets.symmetric(horizontal: 6.w); - backgroundColor = Colors.transparent; - gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: gridViewCount, - mainAxisSpacing: 12.w, - crossAxisSpacing: 12.w, - childAspectRatio: 0.78, - ); - loadData(1); - } - - @override - void dispose() { - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.transparent, - body: buildList(context), - ); - } - - @override - Widget buildItem(StoreListResBean res) { - return GestureDetector( - child: Container( - decoration: BoxDecoration( - color: - res.isSelecte - ? Color(0xff18F2B1).withOpacity(0.1) - : SCGlobalConfig.businessLogicStrategy - .getStoreItemBackgroundColor(), - border: Border.all( - color: - res.isSelecte - ? SCGlobalConfig.businessLogicStrategy - .getStoreItemSelectedBorderColor() - : SCGlobalConfig.businessLogicStrategy - .getStoreItemUnselectedBorderColor(), - width: 1.w, - ), - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - child: Stack( - children: [ - Column( - children: [ - SizedBox(height: 10.w), - netImage( - url: res.res.propsResources?.cover ?? "", - width: 55.w, - height: 55.w, - ), - SizedBox(height: 3.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - text( - res.res.propsResources?.name ?? "", - textColor: Colors.white, - fontSize: 11.sp, - ), - ], - ), - SizedBox(height: 3.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 10.w), - Expanded( - child: Text.rich( - textAlign: TextAlign.center, - TextSpan( - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Image.asset( - SCGlobalConfig.businessLogicStrategy.getStoreItemGoldIcon(), - width: 22.w, - ), - ), - TextSpan(text: " "), - disCount < 1 - ? TextSpan( - text: "${res.res.propsPrices![0].amount}", - style: TextStyle( - fontSize: 12.sp, - color: SCGlobalConfig.businessLogicStrategy.getStoreItemPriceTextColor(), - fontWeight: FontWeight.w600, - decoration: TextDecoration.lineThrough, - ), - ) - : TextSpan(), - disCount < 1 ? TextSpan(text: " ") : TextSpan(), - TextSpan( - text: - "${((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(), - fontWeight: FontWeight.w600, - ), - ), - ], - ), - strutStyle: StrutStyle( - height: 1.3, // 行高倍数 - fontWeight: FontWeight.w500, - forceStrutHeight: true, // 强制应用行高 - ), - ), - ), - SizedBox(width: 10.w), - ], - ), - ], - ), - ], - ), - ), - onTap: () { - _selectItem(res); - _showDetail(res.res); - }, - ); - } - - - ///加载数据 - @override - loadPage({ - required int page, - required Function(List) onSuccess, - Function? onErr, - }) async { - try { - List beans = []; - var storeList = await SCStoreRepositoryImp().storeList( - SCCurrencyType.GOLD.name, - SCPropsType.RIDE.name, - ); - for (var value in storeList) { - beans.add(StoreListResBean(value, false)); - } - onSuccess(beans); - } catch (e) { - if (onErr != null) { - onErr(); - } - } - } - - void _showDetail(StoreListRes res) { - SmartDialog.show( - tag: "showPropsDetail", - alignment: Alignment.bottomCenter, - animationType: SmartAnimationType.fade, - builder: (_) { - return PropsStoreMountainsDetailDialog(res, disCount); - }, - ); - } - - void _selectItem(StoreListResBean res) { - for (var value in items) { - value.isSelecte = false; - } - res.isSelecte = true; - setState(() {}); - } -} +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/sc_page_list.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'; +import '../../../shared/data_sources/models/enum/sc_props_type.dart'; + +///坐骑 +class StoreMountainsPage extends SCPageList { + StoreMountainsPage(); + + @override + _StoreMountainsPageState createState() => _StoreMountainsPageState(); +} + +class _StoreMountainsPageState + extends SCPageListState { + //折扣 + double disCount = 1; + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.78, + ); + loadData(1); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: + items.isEmpty && isLoading + ? const SCStoreGridSkeleton() + : buildList(context), + ); + } + + @override + Widget buildItem(StoreListResBean res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: + res.isSelecte + ? const Color(0xff18F2B1).withValues(alpha: 0.1) + : SCGlobalConfig.businessLogicStrategy + .getStoreItemBackgroundColor(), + border: Border.all( + color: + res.isSelecte + ? SCGlobalConfig.businessLogicStrategy + .getStoreItemSelectedBorderColor() + : SCGlobalConfig.businessLogicStrategy + .getStoreItemUnselectedBorderColor(), + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + children: [ + Column( + children: [ + SizedBox(height: 10.w), + netImage( + url: res.res.propsResources?.cover ?? "", + width: 55.w, + height: 55.w, + ), + SizedBox(height: 3.w), + buildStoreBagItemTitle( + res.res.propsResources?.name ?? "", + textColor: Colors.white, + fontSize: 11.sp, + ), + SizedBox(height: 3.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 10.w), + Expanded( + child: Text.rich( + textAlign: TextAlign.center, + TextSpan( + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Image.asset( + SCGlobalConfig.businessLogicStrategy + .getStoreItemGoldIcon(), + width: 22.w, + ), + ), + TextSpan(text: " "), + disCount < 1 + ? TextSpan( + text: "${res.res.propsPrices![0].amount}", + style: TextStyle( + fontSize: 12.sp, + color: + SCGlobalConfig.businessLogicStrategy + .getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ), + ) + : TextSpan(), + disCount < 1 ? TextSpan(text: " ") : TextSpan(), + TextSpan( + text: + "${((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(), + fontWeight: FontWeight.w600, + ), + ), + ], + ), + strutStyle: StrutStyle( + height: 1.3, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + ), + ), + SizedBox(width: 10.w), + ], + ), + ], + ), + ], + ), + ), + onTap: () { + _selectItem(res); + _showDetail(res.res); + }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + List beans = []; + var storeList = await SCStoreRepositoryImp().storeList( + SCCurrencyType.GOLD.name, + SCPropsType.RIDE.name, + ); + for (var value in storeList) { + beans.add(StoreListResBean(value, false)); + } + onSuccess(beans); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _showDetail(StoreListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsStoreMountainsDetailDialog(res, disCount); + }, + ); + } + + void _selectItem(StoreListResBean res) { + for (var value in items) { + value.isSelecte = false; + } + res.isSelecte = true; + setState(() {}); + } +} diff --git a/lib/modules/store/theme/store_theme_page.dart b/lib/modules/store/theme/store_theme_page.dart index 14352f4..2fa5a00 100644 --- a/lib/modules/store/theme/store_theme_page.dart +++ b/lib/modules/store/theme/store_theme_page.dart @@ -1,239 +1,233 @@ - -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/modules/store/headdress/store_headdress_page.dart'; - -import '../../../shared/data_sources/models/enum/sc_currency_type.dart'; -import '../../../shared/data_sources/models/enum/sc_props_type.dart'; - -///房间主题 -class StoreThemePage extends SCPageList { - - StoreThemePage(); - - @override - _StoreThemePagePageState createState() => _StoreThemePagePageState(); -} - -class _StoreThemePagePageState - extends SCPageListState { - - - //折扣 - double disCount = 1; - - @override - void initState() { - super.initState(); - enablePullUp = false; - isGridView = true; - gridViewCount = 3; - padding = EdgeInsets.symmetric(horizontal: 6.w); - backgroundColor = Colors.transparent; - gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: gridViewCount, - mainAxisSpacing: 12.w, - crossAxisSpacing: 12.w, - childAspectRatio: 0.78, - ); - loadData(1); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.transparent, - body: buildList(context), - ); - } - - @override - Widget buildItem(StoreListResBean res) { - return GestureDetector( - child: Container( - decoration: BoxDecoration( - color: - res.isSelecte - ? Color(0xff18F2B1).withOpacity(0.1) - : SCGlobalConfig.businessLogicStrategy - .getStoreItemBackgroundColor(), - border: Border.all( - color: - res.isSelecte - ? SCGlobalConfig.businessLogicStrategy - .getStoreItemSelectedBorderColor() - : SCGlobalConfig.businessLogicStrategy - .getStoreItemUnselectedBorderColor(), - width: 1.w, - ), - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - child: Stack( - children: [ - Column( - children: [ - SizedBox(height: 10.w), - netImage( - url: res.res.propsResources?.cover ?? "", - width: 55.w, - height: 55.w, - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - SizedBox(height: 3.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - text( - res.res.propsResources?.name ?? "", - textColor: Colors.white, - fontSize: 11.sp, - ), - ], - ), - SizedBox(height: 3.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 10.w), - Expanded( - child: Text.rich( - textAlign: TextAlign.center, - TextSpan( - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Image.asset( - SCGlobalConfig.businessLogicStrategy.getStoreItemGoldIcon(), - width: 22.w, - ), - ), - TextSpan(text: " "), - disCount < 1 - ? TextSpan( - text: "${res.res.propsPrices![0].amount}", - style: TextStyle( - fontSize: 12.sp, - color: SCGlobalConfig.businessLogicStrategy.getStoreItemPriceTextColor(), - fontWeight: FontWeight.w600, - decoration: TextDecoration.lineThrough, - ), - ) - : TextSpan(), - disCount < 1 ? TextSpan(text: " ") : TextSpan(), - TextSpan( - text: - "${((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(), - fontWeight: FontWeight.w600, - ), - ), - ], - ), - strutStyle: StrutStyle( - height: 1.3, // 行高倍数 - fontWeight: FontWeight.w500, - forceStrutHeight: true, // 强制应用行高 - ), - ), - ), - SizedBox(width: 10.w), - ], - ), - ], - ), - ], - ), - ), - onTap: () { - _selectItem(res); - _showDetail(res.res); - }, - ); - } - - void _showDetail(StoreListRes res) { - SmartDialog.show( - tag: "showPropsDetail", - alignment: Alignment.bottomCenter, - animationType: SmartAnimationType.fade, - builder: (_) { - return PropsStoreThemeDetailDialog(res, disCount); - }, - ); - } - - - ///加载数据 - @override - loadPage({ - required int page, - required Function(List) onSuccess, - Function? onErr, - }) async { - try { - List beans = []; - var storeList = await SCStoreRepositoryImp().storeList( - SCCurrencyType.GOLD.name, - SCPropsType.THEME.name, - ); - for (var value in storeList) { - beans.add(StoreListResBean(value, false)); - } - onSuccess(beans); - } catch (e) { - if (onErr != null) { - onErr(); - } - } - } - - void _selectItem(StoreListResBean res) { - for (var value in items) { - value.isSelecte = false; - } - res.isSelecte = true; - setState(() {}); - } - - void _buy(StoreListResBean res) { - num days = 0; - if (res.res.propsPrices!.length > 1) { - days = res.res.propsPrices![1].days ?? 0; - } else { - days = res.res.propsPrices![0].days ?? 0; - } - SCStoreRepositoryImp() - .storePurchasing( - res.res.id ?? "", - SCPropsType.THEME.name, - SCCurrencyType.GOLD.name, - "$days", - ) - .then((value) { - SCTts.show(SCAppLocalizations.of(context)!.purchaseIsSuccessful); - Provider.of( - context, - listen: false, - ).updateBalance(value); - }); - } -} +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_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/sc_tts.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/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'; +import '../../../shared/data_sources/models/enum/sc_props_type.dart'; + +///房间主题 +class StoreThemePage extends SCPageList { + StoreThemePage(); + + @override + _StoreThemePagePageState createState() => _StoreThemePagePageState(); +} + +class _StoreThemePagePageState + extends SCPageListState { + //折扣 + double disCount = 1; + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.78, + ); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: + items.isEmpty && isLoading + ? const SCStoreGridSkeleton() + : buildList(context), + ); + } + + @override + Widget buildItem(StoreListResBean res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: + res.isSelecte + ? const Color(0xff18F2B1).withValues(alpha: 0.1) + : SCGlobalConfig.businessLogicStrategy + .getStoreItemBackgroundColor(), + border: Border.all( + color: + res.isSelecte + ? SCGlobalConfig.businessLogicStrategy + .getStoreItemSelectedBorderColor() + : SCGlobalConfig.businessLogicStrategy + .getStoreItemUnselectedBorderColor(), + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + children: [ + Column( + children: [ + SizedBox(height: 10.w), + netImage( + url: res.res.propsResources?.cover ?? "", + width: 55.w, + height: 55.w, + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + SizedBox(height: 3.w), + buildStoreBagItemTitle( + res.res.propsResources?.name ?? "", + textColor: Colors.white, + fontSize: 11.sp, + ), + SizedBox(height: 3.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 10.w), + Expanded( + child: Text.rich( + textAlign: TextAlign.center, + TextSpan( + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Image.asset( + SCGlobalConfig.businessLogicStrategy + .getStoreItemGoldIcon(), + width: 22.w, + ), + ), + TextSpan(text: " "), + disCount < 1 + ? TextSpan( + text: "${res.res.propsPrices![0].amount}", + style: TextStyle( + fontSize: 12.sp, + color: + SCGlobalConfig.businessLogicStrategy + .getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ), + ) + : TextSpan(), + disCount < 1 ? TextSpan(text: " ") : TextSpan(), + TextSpan( + text: + "${((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(), + fontWeight: FontWeight.w600, + ), + ), + ], + ), + strutStyle: StrutStyle( + height: 1.3, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + ), + ), + SizedBox(width: 10.w), + ], + ), + ], + ), + ], + ), + ), + onTap: () { + _selectItem(res); + _showDetail(res.res); + }, + ); + } + + void _showDetail(StoreListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsStoreThemeDetailDialog(res, disCount); + }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + List beans = []; + var storeList = await SCStoreRepositoryImp().storeList( + SCCurrencyType.GOLD.name, + SCPropsType.THEME.name, + ); + for (var value in storeList) { + beans.add(StoreListResBean(value, false)); + } + onSuccess(beans); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _selectItem(StoreListResBean res) { + for (var value in items) { + value.isSelecte = false; + } + res.isSelecte = true; + setState(() {}); + } + + void _buy(StoreListResBean res) { + num days = 0; + if (res.res.propsPrices!.length > 1) { + days = res.res.propsPrices![1].days ?? 0; + } else { + days = res.res.propsPrices![0].days ?? 0; + } + SCStoreRepositoryImp() + .storePurchasing( + res.res.id ?? "", + SCPropsType.THEME.name, + SCCurrencyType.GOLD.name, + "$days", + ) + .then((value) { + SCTts.show(SCAppLocalizations.of(context)!.purchaseIsSuccessful); + Provider.of( + context, + listen: false, + ).updateBalance(value); + }); + } +} diff --git a/lib/modules/user/my_items/chatbox/bags_chatbox_page.dart b/lib/modules/user/my_items/chatbox/bags_chatbox_page.dart index c64a18a..e438960 100644 --- a/lib/modules/user/my_items/chatbox/bags_chatbox_page.dart +++ b/lib/modules/user/my_items/chatbox/bags_chatbox_page.dart @@ -1,383 +1,383 @@ -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/shared/data_sources/sources/local/user_manager.dart'; -import 'package:provider/provider.dart'; -import 'package:yumi/ui_kit/components/dialog/dialog_base.dart'; -import 'package:yumi/ui_kit/components/sc_page_list.dart'; -import 'package:yumi/ui_kit/components/sc_tts.dart'; -import 'package:yumi/app/routes/sc_fluro_navigator.dart'; -import 'package:yumi/shared/tools/sc_loading_manager.dart'; -import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart'; -import 'package:yumi/shared/business_logic/models/res/bags_list_res.dart'; -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/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 '../../../../shared/data_sources/models/enum/sc_props_type.dart'; -import '../../../../ui_kit/components/sc_debounce_widget.dart'; -import '../../../../ui_kit/theme/socialchat_theme.dart'; - -///背包-气泡框 -class BagsChatboxPage extends SCPageList { - @override - _BagsChatboxPageState createState() => _BagsChatboxPageState(); -} - -class _BagsChatboxPageState - extends SCPageListState { - ///自己当前佩戴的气泡框 - PropsResources? myChatbox; - BagsListRes? selecteChatbox; - - @override - void initState() { - super.initState(); - enablePullUp = false; - isGridView = true; - gridViewCount = 3; - padding = EdgeInsets.symmetric(horizontal: 6.w); - backgroundColor = Colors.transparent; - gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: gridViewCount, - mainAxisSpacing: 12.w, - crossAxisSpacing: 12.w, - childAspectRatio: 0.88, - ); - myChatbox = AccountStorage().getChatbox(); - loadData(1); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.transparent, - body: Column( - children: [ - Expanded(child: buildList(context)), - selecteChatbox != null ? SizedBox(height: 10.w) : Container(), - selecteChatbox != null - ? Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(12.w), - topRight: Radius.circular(12.w), - ), - color: Color(0xff18F2B1).withOpacity(0.1), - ), - child: Column( - children: [ - SizedBox(height: 5.w), - Row( - spacing: 5.w, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - text( - "${SCAppLocalizations.of(context)!.expirationTime}:", - textColor: Colors.white, - fontSize: 12.sp, - ), - CountdownTimer( - fontSize: 11.sp, - color: SocialChatTheme.primaryLight, - expiryDate: DateTime.fromMillisecondsSinceEpoch( - selecteChatbox?.expireTime ?? 0, - ), - ), - ], - ), - SizedBox(height: 10.w), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SCDebounceWidget( - child: Container( - alignment: Alignment.center, - width: 120.w, - height: 35.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.w), - color: SocialChatTheme.primaryLight, - ), - child: text( - SCAppLocalizations.of(context)!.renewal, - fontWeight: FontWeight.w600, - fontSize: 14.sp, - textColor: Colors.white, - ), - ), - onTap: () { - ///过期 续费操作 - SCNavigatorUtils.push( - context, - StoreRoute.list, - replace: false, - ); - }, - ), - SizedBox(width: 10.w), - SCDebounceWidget( - child: Container( - alignment: Alignment.center, - width: 120.w, - height: 35.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.w), - color: - myChatbox?.id == - selecteChatbox?.propsResources?.id - ? Colors.white - : SocialChatTheme.primaryLight, - ), - child: text( - myChatbox?.id == - selecteChatbox?.propsResources?.id - ? SCAppLocalizations.of(context)!.inUse - : SCAppLocalizations.of(context)!.use, - fontWeight: FontWeight.w600, - fontSize: 14.sp, - textColor: - myChatbox?.id != - selecteChatbox?.propsResources?.id - ? Colors.white - : Color(0xffB1B1B1), - ), - ), - onTap: () { - if (myChatbox?.id != - selecteChatbox?.propsResources?.id) { - ///佩戴 - SmartDialog.show( - tag: "showConfirmDialog", - alignment: Alignment.center, - debounce: true, - animationType: SmartAnimationType.fade, - builder: (_) { - return MsgDialog( - title: SCAppLocalizations.of(context)!.tips, - msg: - SCAppLocalizations.of( - context, - )!.confirmUseTips, - btnText: - SCAppLocalizations.of(context)!.confirm, - onEnsure: () { - _use(selecteChatbox!, false); - }, - ); - }, - ); - } else { - ///卸下 - SmartDialog.show( - tag: "showConfirmDialog", - alignment: Alignment.center, - debounce: true, - animationType: SmartAnimationType.fade, - builder: (_) { - return MsgDialog( - title: SCAppLocalizations.of(context)!.tips, - msg: - SCAppLocalizations.of( - context, - )!.confirmUnUseTips, - btnText: - SCAppLocalizations.of(context)!.confirm, - onEnsure: () { - _use(selecteChatbox!, true); - }, - ); - }, - ); - } - }, - ), - ], - ), - SizedBox(height: 10.w), - ], - ), - ) - : Container(), - ], - ), - ); - } - - @override - Widget buildItem(BagsListRes res) { - return GestureDetector( - child: Container( - decoration:BoxDecoration( - color:Color(0xff18F2B1).withOpacity(0.1), - border: Border.all( - color: - selecteChatbox?.propsResources?.id == res.propsResources?.id - ? SocialChatTheme.primaryLight - : Colors.transparent, - width: 1.w, - ), - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ) , - child: Stack( - alignment: Alignment.center, - children: [ - Column( - children: [ - SizedBox(height: 25.w), - netImage( - url: res.propsResources?.cover ?? "", - height: 35.w, - fit: BoxFit.contain, - ), - SizedBox(height: 8.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - text( - res.propsResources?.name ?? "", - textColor: Colors.black, - fontSize: 12.sp, - ), - ], - ), - ], - ), - PositionedDirectional( - top: 0, - start: 0, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 5.w), - decoration: BoxDecoration( - color: SocialChatTheme.primaryLight, - borderRadius: BorderRadiusDirectional.only( - topStart: Radius.circular(6.w), - topEnd: Radius.circular(12.w), - bottomEnd: Radius.circular(12.w), - ), - ), - child: Row( - children: [ - CountdownTimer( - fontSize: 9.sp, - color: Colors.white, - expiryDate: DateTime.fromMillisecondsSinceEpoch( - res.expireTime ?? 0, - ), - ), - SizedBox(width: 3.w), - Image.asset( - "sc_images/store/sc_icon_bag_clock.png", - width: 22.w, - height: 22.w, - ), - ], - ), - ), - ), - PositionedDirectional( - bottom: 5.w, - end: 5.w, - child: GestureDetector( - onTap: () { - selecteChatbox = res; - setState(() {}); - _showDetail(res); - }, - behavior: HitTestBehavior.opaque, - child: Container( - padding: EdgeInsets.all(5.w), - child: Image.asset( - "sc_images/store/sc_icon_shop_item_search.png", - width: 18.w, - height: 18.w, - color: Colors.white, - ), - ), - ), - ), - ], - ), - ), - onTap: () { - selecteChatbox = res; - setState(() {}); - }, - ); - } - - ///加载数据 - @override - loadPage({ - required int page, - required Function(List) onSuccess, - Function? onErr, - }) async { - try { - var storeList = await SCStoreRepositoryImp().storeBackpack( - AccountStorage().getCurrentUser()?.userProfile?.id ?? "", - SCPropsType.CHAT_BUBBLE.name, - ); - for (var v in storeList) { - if (v.propsResources?.id == myChatbox?.id) { - selecteChatbox = v; - continue; - } - } - onSuccess(storeList); - } catch (e) { - if (onErr != null) { - onErr(); - } - } - } - - void _showDetail(BagsListRes res) { - SmartDialog.show( - tag: "showPropsDetail", - alignment: Alignment.center, - animationType: SmartAnimationType.fade, - builder: (_) { - return PropsBagChatboxDetailDialog(res); - }, - onDismiss: () { - myChatbox = AccountStorage().getChatbox(); - setState(() {}); - }, - ); - } - - void _use(BagsListRes res, bool unload) { - SCLoadingManager.show(context: context); - SCStoreRepositoryImp() - .switchPropsUse( - SCPropsType.CHAT_BUBBLE.name, - res.propsResources?.id ?? "", - unload, - ) - .then((value) async { - setState(() {}); - if (!unload) { - myChatbox = res.propsResources; - SCTts.show(SCAppLocalizations.of(context)!.successfulWear); - } else { - myChatbox = null; - SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded); - } - Provider.of( - context, - listen: false, - ).fetchUserProfileData(loadGuardCount: false); - SCLoadingManager.hide(); - }) - .catchError((e) { - SCLoadingManager.hide(); - }); - } -} +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/shared/data_sources/sources/local/user_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:yumi/ui_kit/components/dialog/dialog_base.dart'; +import 'package:yumi/ui_kit/components/sc_page_list.dart'; +import 'package:yumi/ui_kit/components/sc_tts.dart'; +import 'package:yumi/app/routes/sc_fluro_navigator.dart'; +import 'package:yumi/shared/tools/sc_loading_manager.dart'; +import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart'; +import 'package:yumi/shared/business_logic/models/res/bags_list_res.dart'; +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/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'; +import '../../../../ui_kit/theme/socialchat_theme.dart'; + +///背包-气泡框 +class BagsChatboxPage extends SCPageList { + @override + _BagsChatboxPageState createState() => _BagsChatboxPageState(); +} + +class _BagsChatboxPageState + extends SCPageListState { + ///自己当前佩戴的气泡框 + PropsResources? myChatbox; + BagsListRes? selecteChatbox; + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.88, + ); + myChatbox = AccountStorage().getChatbox(); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + Expanded( + child: + items.isEmpty && isLoading + ? const SCBagGridSkeleton() + : buildList(context), + ), + selecteChatbox != null ? SizedBox(height: 10.w) : Container(), + selecteChatbox != null + ? Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + color: const Color(0xff18F2B1).withValues(alpha: 0.1), + ), + child: Column( + children: [ + SizedBox(height: 5.w), + Row( + spacing: 5.w, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + "${SCAppLocalizations.of(context)!.expirationTime}:", + textColor: Colors.white, + fontSize: 12.sp, + ), + CountdownTimer( + fontSize: 11.sp, + color: SocialChatTheme.primaryLight, + expiryDate: DateTime.fromMillisecondsSinceEpoch( + selecteChatbox?.expireTime ?? 0, + ), + ), + ], + ), + SizedBox(height: 10.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SCDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 120.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: SocialChatTheme.primaryLight, + ), + child: text( + SCAppLocalizations.of(context)!.renewal, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: Colors.white, + ), + ), + onTap: () { + ///过期 续费操作 + SCNavigatorUtils.push( + context, + StoreRoute.list, + replace: false, + ); + }, + ), + SizedBox(width: 10.w), + SCDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 120.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: + myChatbox?.id == + selecteChatbox?.propsResources?.id + ? Colors.white + : SocialChatTheme.primaryLight, + ), + child: text( + myChatbox?.id == + selecteChatbox?.propsResources?.id + ? SCAppLocalizations.of(context)!.inUse + : SCAppLocalizations.of(context)!.use, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: + myChatbox?.id != + selecteChatbox?.propsResources?.id + ? Colors.white + : Color(0xffB1B1B1), + ), + ), + onTap: () { + if (myChatbox?.id != + selecteChatbox?.propsResources?.id) { + ///佩戴 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: SCAppLocalizations.of(context)!.tips, + msg: + SCAppLocalizations.of( + context, + )!.confirmUseTips, + btnText: + SCAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(selecteChatbox!, false); + }, + ); + }, + ); + } else { + ///卸下 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: SCAppLocalizations.of(context)!.tips, + msg: + SCAppLocalizations.of( + context, + )!.confirmUnUseTips, + btnText: + SCAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(selecteChatbox!, true); + }, + ); + }, + ); + } + }, + ), + ], + ), + SizedBox(height: 10.w), + ], + ), + ) + : Container(), + ], + ), + ); + } + + @override + Widget buildItem(BagsListRes res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: const Color(0xff18F2B1).withValues(alpha: 0.1), + border: Border.all( + color: + selecteChatbox?.propsResources?.id == res.propsResources?.id + ? SocialChatTheme.primaryLight + : Colors.transparent, + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + alignment: Alignment.center, + children: [ + Column( + children: [ + SizedBox(height: 25.w), + netImage( + url: res.propsResources?.cover ?? "", + height: 35.w, + fit: BoxFit.contain, + ), + SizedBox(height: 8.w), + buildStoreBagItemTitle( + res.propsResources?.name ?? "", + textColor: Colors.black, + fontSize: 12.sp, + ), + ], + ), + PositionedDirectional( + top: 0, + start: 0, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5.w), + decoration: BoxDecoration( + color: SocialChatTheme.primaryLight, + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(6.w), + topEnd: Radius.circular(12.w), + bottomEnd: Radius.circular(12.w), + ), + ), + child: Row( + children: [ + CountdownTimer( + fontSize: 9.sp, + color: Colors.white, + expiryDate: DateTime.fromMillisecondsSinceEpoch( + res.expireTime ?? 0, + ), + ), + SizedBox(width: 3.w), + Image.asset( + "sc_images/store/sc_icon_bag_clock.png", + width: 22.w, + height: 22.w, + ), + ], + ), + ), + ), + PositionedDirectional( + bottom: 5.w, + end: 5.w, + child: GestureDetector( + onTap: () { + selecteChatbox = res; + setState(() {}); + _showDetail(res); + }, + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.all(5.w), + child: Image.asset( + "sc_images/store/sc_icon_shop_item_search.png", + width: 18.w, + height: 18.w, + color: Colors.white, + ), + ), + ), + ), + ], + ), + ), + onTap: () { + selecteChatbox = res; + setState(() {}); + }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var storeList = await SCStoreRepositoryImp().storeBackpack( + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + SCPropsType.CHAT_BUBBLE.name, + ); + for (var v in storeList) { + if (v.propsResources?.id == myChatbox?.id) { + selecteChatbox = v; + continue; + } + } + onSuccess(storeList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _showDetail(BagsListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsBagChatboxDetailDialog(res); + }, + onDismiss: () { + myChatbox = AccountStorage().getChatbox(); + setState(() {}); + }, + ); + } + + void _use(BagsListRes res, bool unload) { + SCLoadingManager.show(context: context); + SCStoreRepositoryImp() + .switchPropsUse( + SCPropsType.CHAT_BUBBLE.name, + res.propsResources?.id ?? "", + unload, + ) + .then((value) async { + setState(() {}); + if (!unload) { + myChatbox = res.propsResources; + SCTts.show(SCAppLocalizations.of(context)!.successfulWear); + } else { + myChatbox = null; + SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded); + } + Provider.of( + context, + listen: false, + ).fetchUserProfileData(loadGuardCount: false); + SCLoadingManager.hide(); + }) + .catchError((e) { + SCLoadingManager.hide(); + }); + } +} diff --git a/lib/modules/user/my_items/headdress/bags_headdress_page.dart b/lib/modules/user/my_items/headdress/bags_headdress_page.dart index 25009e4..a4ad6da 100644 --- a/lib/modules/user/my_items/headdress/bags_headdress_page.dart +++ b/lib/modules/user/my_items/headdress/bags_headdress_page.dart @@ -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 == - selecteHeaddress?.propsResources?.id - ?Colors.white:SocialChatTheme.primaryLight, + color: + myHeaddress?.id == + selecteHeaddress?.propsResources?.id + ? 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,15 +242,10 @@ class _BagsHeaddressPageState height: 55.w, ), SizedBox(height: 8.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - text( - res.propsResources?.name ?? "", - textColor: Colors.white, - fontSize: 12.sp, - ), - ], + buildStoreBagItemTitle( + res.propsResources?.name ?? "", + textColor: Colors.white, + fontSize: 12.sp, ), ], ), diff --git a/lib/modules/user/my_items/mountains/bags_mountains_page.dart b/lib/modules/user/my_items/mountains/bags_mountains_page.dart index 11e3dbf..8b38584 100644 --- a/lib/modules/user/my_items/mountains/bags_mountains_page.dart +++ b/lib/modules/user/my_items/mountains/bags_mountains_page.dart @@ -1,368 +1,368 @@ -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/widgets/bag/props_bag_mountains_detail_dialog.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/app/routes/sc_fluro_navigator.dart'; -import 'package:yumi/shared/tools/sc_loading_manager.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/bags_list_res.dart'; -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 '../../../../shared/data_sources/models/enum/sc_props_type.dart'; -import '../../../../ui_kit/components/sc_debounce_widget.dart'; -import '../../../../ui_kit/components/dialog/dialog_base.dart'; -import '../../../../ui_kit/theme/socialchat_theme.dart'; - -///坐骑 -class BagsMountainsPage extends SCPageList { - @override - _BagsMountainsPageState createState() => _BagsMountainsPageState(); -} - -class _BagsMountainsPageState - extends SCPageListState { - BagsListRes? selecteMountains; - - ///自己当前佩戴的坐骑 - PropsResources? myMountains; - - @override - void initState() { - super.initState(); - enablePullUp = false; - isGridView = true; - gridViewCount = 3; - padding = EdgeInsets.symmetric(horizontal: 6.w); - backgroundColor = Colors.transparent; - gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: gridViewCount, - mainAxisSpacing: 12.w, - crossAxisSpacing: 12.w, - childAspectRatio: 0.88, - ); - myMountains = AccountStorage().getMountains(); - loadData(1); - } - - @override - void dispose() { - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.transparent, - body: Column( - children: [ - Expanded(child: buildList(context)), - selecteMountains != null ? SizedBox(height: 10.w) : Container(), - selecteMountains != null - ? Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(12.w), - topRight: Radius.circular(12.w), - ), - color: Color(0xff18F2B1).withOpacity(0.1), - ), - child: Column( - children: [ - SizedBox(height: 5.w), - Row( - spacing: 5.w, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - text( - "${SCAppLocalizations.of(context)!.expirationTime}:", - textColor: Colors.white, - fontSize: 12.sp, - ), - CountdownTimer( - fontSize: 11.sp, - color: SocialChatTheme.primaryLight, - expiryDate: DateTime.fromMillisecondsSinceEpoch( - selecteMountains?.expireTime ?? 0, - ), - ), - ], - ), - SizedBox(height: 10.w), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SCDebounceWidget( - child: Container( - alignment: Alignment.center, - width: 120.w, - height: 35.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.w), - color: SocialChatTheme.primaryLight, - ), - child: text( - SCAppLocalizations.of(context)!.renewal, - fontWeight: FontWeight.w600, - fontSize: 14.sp, - textColor: Colors.white, - ), - ), - onTap: () { - ///过期 续费操作 - SCNavigatorUtils.push( - context, - StoreRoute.list, - replace: false, - ); - }, - ), - SizedBox(width: 10.w), - SCDebounceWidget( - child: Container( - alignment: Alignment.center, - width: 120.w, - height: 35.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.w), - color: - myMountains?.id == - selecteMountains?.propsResources?.id - ? Colors.white - : SocialChatTheme.primaryLight, - ), - child: text( - myMountains?.id == - selecteMountains?.propsResources?.id - ? SCAppLocalizations.of(context)!.inUse - : SCAppLocalizations.of(context)!.use, - fontSize: 14.sp, - fontWeight: FontWeight.w600, - textColor: - myMountains?.id != - selecteMountains?.propsResources?.id - ? Colors.white - : Color(0xffB1B1B1), - ), - ), - onTap: () { - if (myMountains?.id != - selecteMountains?.propsResources?.id) { - ///佩戴 - SmartDialog.show( - tag: "showConfirmDialog", - alignment: Alignment.center, - debounce: true, - animationType: SmartAnimationType.fade, - builder: (_) { - return MsgDialog( - title: SCAppLocalizations.of(context)!.tips, - msg: - SCAppLocalizations.of( - context, - )!.confirmUseTips, - btnText: - SCAppLocalizations.of(context)!.confirm, - onEnsure: () { - _use(selecteMountains!, false); - }, - ); - }, - ); - } else { - ///卸下 - SmartDialog.show( - tag: "showConfirmDialog", - alignment: Alignment.center, - debounce: true, - animationType: SmartAnimationType.fade, - builder: (_) { - return MsgDialog( - title: SCAppLocalizations.of(context)!.tips, - msg: - SCAppLocalizations.of( - context, - )!.confirmUnUseTips, - btnText: - SCAppLocalizations.of(context)!.confirm, - onEnsure: () { - _use(selecteMountains!, true); - }, - ); - }, - ); - } - }, - ), - ], - ), - SizedBox(height: 10.w), - ], - ), - ) - : Container(), - ], - ), - ); - } - - @override - Widget buildItem(BagsListRes res) { - return GestureDetector( - child: Container( - decoration: BoxDecoration( - color:Color(0xff18F2B1).withOpacity(0.1), - border: Border.all( - color: - selecteMountains?.propsResources?.id == res.propsResources?.id - ? SocialChatTheme.primaryLight - : Colors.transparent, - width: 1.w, - ), - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - child: Stack( - alignment: Alignment.center, - children: [ - Column( - children: [ - SizedBox(height: 25.w), - netImage( - url: res.propsResources?.cover ?? "", - width: 55.w, - height: 55.w, - ), - SizedBox(height: 8.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - text( - res.propsResources?.name ?? "", - textColor: Colors.white, - fontSize: 12.sp, - ), - ], - ), - ], - ), - PositionedDirectional( - top: 0, - start: 0, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 5.w), - decoration: BoxDecoration( - color: SocialChatTheme.primaryLight, - borderRadius: BorderRadiusDirectional.only( - topStart: Radius.circular(6.w), - topEnd: Radius.circular(12.w), - bottomEnd: Radius.circular(12.w), - ), - ), - child: Row( - children: [ - CountdownTimer( - fontSize: 9.sp, - color: Colors.white, - expiryDate: DateTime.fromMillisecondsSinceEpoch( - res.expireTime ?? 0, - ), - ), - SizedBox(width: 3.w), - Image.asset( - "sc_images/store/sc_icon_bag_clock.png", - width: 22.w, - height: 22.w, - ), - ], - ), - ), - ), - ], - ), - ), - onTap: () { - selecteMountains = res; - setState(() {}); - }, - ); - } - - ///加载数据 - @override - loadPage({ - required int page, - required Function(List) onSuccess, - Function? onErr, - }) async { - try { - var storeList = await SCStoreRepositoryImp().storeBackpack( - AccountStorage().getCurrentUser()?.userProfile?.id ?? "", - SCPropsType.RIDE.name, - ); - for (var v in storeList) { - if (v.propsResources?.id == myMountains?.id) { - selecteMountains = v; - continue; - } - } - onSuccess(storeList); - } catch (e) { - if (onErr != null) { - onErr(); - } - } - } - - void _showDetail(BagsListRes res) { - SmartDialog.show( - tag: "showPropsDetail", - alignment: Alignment.center, - animationType: SmartAnimationType.fade, - builder: (_) { - return PropsBagMountainsDetailDialog(res); - }, - onDismiss: () { - myMountains = AccountStorage().getMountains(); - setState(() {}); - }, - ); - } - - void _use(BagsListRes res, bool unload) { - SCLoadingManager.show(context: context); - SCStoreRepositoryImp() - .switchPropsUse( - SCPropsType.RIDE.name, - res.propsResources?.id ?? "", - unload, - ) - .then((value) async { - setState(() {}); - if (!unload) { - myMountains = res.propsResources; - SCTts.show(SCAppLocalizations.of(context)!.successfulWear); - } else { - myMountains = null; - SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded); - } - Provider.of( - context, - listen: false, - ).fetchUserProfileData(loadGuardCount: false); - SCLoadingManager.hide(); - }) - .catchError((e) { - SCLoadingManager.hide(); - }); - } -} +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/widgets/bag/props_bag_mountains_detail_dialog.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/app/routes/sc_fluro_navigator.dart'; +import 'package:yumi/shared/tools/sc_loading_manager.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/bags_list_res.dart'; +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'; +import '../../../../ui_kit/components/dialog/dialog_base.dart'; +import '../../../../ui_kit/theme/socialchat_theme.dart'; + +///坐骑 +class BagsMountainsPage extends SCPageList { + @override + _BagsMountainsPageState createState() => _BagsMountainsPageState(); +} + +class _BagsMountainsPageState + extends SCPageListState { + BagsListRes? selecteMountains; + + ///自己当前佩戴的坐骑 + PropsResources? myMountains; + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.88, + ); + myMountains = AccountStorage().getMountains(); + loadData(1); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + Expanded( + child: + items.isEmpty && isLoading + ? const SCBagGridSkeleton() + : buildList(context), + ), + selecteMountains != null ? SizedBox(height: 10.w) : Container(), + selecteMountains != null + ? Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + color: const Color(0xff18F2B1).withValues(alpha: 0.1), + ), + child: Column( + children: [ + SizedBox(height: 5.w), + Row( + spacing: 5.w, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + "${SCAppLocalizations.of(context)!.expirationTime}:", + textColor: Colors.white, + fontSize: 12.sp, + ), + CountdownTimer( + fontSize: 11.sp, + color: SocialChatTheme.primaryLight, + expiryDate: DateTime.fromMillisecondsSinceEpoch( + selecteMountains?.expireTime ?? 0, + ), + ), + ], + ), + SizedBox(height: 10.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SCDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 120.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: SocialChatTheme.primaryLight, + ), + child: text( + SCAppLocalizations.of(context)!.renewal, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: Colors.white, + ), + ), + onTap: () { + ///过期 续费操作 + SCNavigatorUtils.push( + context, + StoreRoute.list, + replace: false, + ); + }, + ), + SizedBox(width: 10.w), + SCDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 120.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: + myMountains?.id == + selecteMountains?.propsResources?.id + ? Colors.white + : SocialChatTheme.primaryLight, + ), + child: text( + myMountains?.id == + selecteMountains?.propsResources?.id + ? SCAppLocalizations.of(context)!.inUse + : SCAppLocalizations.of(context)!.use, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: + myMountains?.id != + selecteMountains?.propsResources?.id + ? Colors.white + : Color(0xffB1B1B1), + ), + ), + onTap: () { + if (myMountains?.id != + selecteMountains?.propsResources?.id) { + ///佩戴 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: SCAppLocalizations.of(context)!.tips, + msg: + SCAppLocalizations.of( + context, + )!.confirmUseTips, + btnText: + SCAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(selecteMountains!, false); + }, + ); + }, + ); + } else { + ///卸下 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: SCAppLocalizations.of(context)!.tips, + msg: + SCAppLocalizations.of( + context, + )!.confirmUnUseTips, + btnText: + SCAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(selecteMountains!, true); + }, + ); + }, + ); + } + }, + ), + ], + ), + SizedBox(height: 10.w), + ], + ), + ) + : Container(), + ], + ), + ); + } + + @override + Widget buildItem(BagsListRes res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: const Color(0xff18F2B1).withValues(alpha: 0.1), + border: Border.all( + color: + selecteMountains?.propsResources?.id == res.propsResources?.id + ? SocialChatTheme.primaryLight + : Colors.transparent, + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + alignment: Alignment.center, + children: [ + Column( + children: [ + SizedBox(height: 25.w), + netImage( + url: res.propsResources?.cover ?? "", + width: 55.w, + height: 55.w, + ), + SizedBox(height: 8.w), + buildStoreBagItemTitle( + res.propsResources?.name ?? "", + textColor: Colors.white, + fontSize: 12.sp, + ), + ], + ), + PositionedDirectional( + top: 0, + start: 0, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5.w), + decoration: BoxDecoration( + color: SocialChatTheme.primaryLight, + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(6.w), + topEnd: Radius.circular(12.w), + bottomEnd: Radius.circular(12.w), + ), + ), + child: Row( + children: [ + CountdownTimer( + fontSize: 9.sp, + color: Colors.white, + expiryDate: DateTime.fromMillisecondsSinceEpoch( + res.expireTime ?? 0, + ), + ), + SizedBox(width: 3.w), + Image.asset( + "sc_images/store/sc_icon_bag_clock.png", + width: 22.w, + height: 22.w, + ), + ], + ), + ), + ), + ], + ), + ), + onTap: () { + selecteMountains = res; + setState(() {}); + }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var storeList = await SCStoreRepositoryImp().storeBackpack( + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + SCPropsType.RIDE.name, + ); + for (var v in storeList) { + if (v.propsResources?.id == myMountains?.id) { + selecteMountains = v; + continue; + } + } + onSuccess(storeList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _showDetail(BagsListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsBagMountainsDetailDialog(res); + }, + onDismiss: () { + myMountains = AccountStorage().getMountains(); + setState(() {}); + }, + ); + } + + void _use(BagsListRes res, bool unload) { + SCLoadingManager.show(context: context); + SCStoreRepositoryImp() + .switchPropsUse( + SCPropsType.RIDE.name, + res.propsResources?.id ?? "", + unload, + ) + .then((value) async { + setState(() {}); + if (!unload) { + myMountains = res.propsResources; + SCTts.show(SCAppLocalizations.of(context)!.successfulWear); + } else { + myMountains = null; + SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded); + } + Provider.of( + context, + listen: false, + ).fetchUserProfileData(loadGuardCount: false); + SCLoadingManager.hide(); + }) + .catchError((e) { + SCLoadingManager.hide(); + }); + } +} diff --git a/lib/modules/user/my_items/theme/bags_theme_page.dart b/lib/modules/user/my_items/theme/bags_theme_page.dart index 1d3168b..3d030fe 100644 --- a/lib/modules/user/my_items/theme/bags_theme_page.dart +++ b/lib/modules/user/my_items/theme/bags_theme_page.dart @@ -1,359 +1,364 @@ -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'; -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/theme/socialchat_theme.dart'; -import 'package:yumi/ui_kit/components/sc_page_list.dart'; -import 'package:yumi/app/routes/sc_fluro_navigator.dart'; -import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart'; -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 '../../../../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'; -import '../../../../services/audio/rtc_manager.dart'; -import '../../../../services/audio/rtm_manager.dart'; -import '../../../../ui_kit/components/sc_debounce_widget.dart'; -import '../../../../ui_kit/components/dialog/dialog_base.dart'; -import '../../../../ui_kit/components/sc_tts.dart'; -import '../../../../ui_kit/widgets/room/room_msg_item.dart'; - -///背包-房间主题 -class BagsThemePage extends SCPageList { - @override - _BagsThemePageState createState() => _BagsThemePageState(); -} - -class _BagsThemePageState - extends SCPageListState { - SCRoomThemeListRes? selecteTheme; - - @override - void initState() { - super.initState(); - enablePullUp = false; - isGridView = true; - gridViewCount = 3; - padding = EdgeInsets.symmetric(horizontal: 6.w); - backgroundColor = Colors.transparent; - gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: gridViewCount, - mainAxisSpacing: 12.w, - crossAxisSpacing: 12.w, - childAspectRatio: 0.88, - ); - loadData(1); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.transparent, - body: Column( - children: [ - Expanded(child: buildList(context)), - selecteTheme != null ? SizedBox(height: 10.w) : Container(), - selecteTheme != null - ? Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(12.w), - topRight: Radius.circular(12.w), - ), - color: Color(0xff18F2B1).withOpacity(0.1), - ), - child: Column( - children: [ - SizedBox(height: 5.w), - Row( - spacing: 5.w, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - text( - "${SCAppLocalizations.of(context)!.expirationTime}:", - textColor: Colors.white, - fontSize: 12.sp, - ), - CountdownTimer( - fontSize: 11.sp, - color: SocialChatTheme.primaryLight, - expiryDate: DateTime.fromMillisecondsSinceEpoch( - selecteTheme?.expireTime ?? 0, - ), - ), - ], - ), - SizedBox(height: 10.w), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SCDebounceWidget( - child: Container( - alignment: Alignment.center, - width: 120.w, - height: 35.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.w), - color: SocialChatTheme.primaryLight, - ), - child: text( - SCAppLocalizations.of(context)!.renewal, - fontWeight: FontWeight.w600, - fontSize: 14.sp, - textColor: Colors.white, - ), - ), - onTap: () { - ///过期 续费操作 - SCNavigatorUtils.push( - context, - StoreRoute.list, - replace: false, - ); - }, - ), - SizedBox(width: 10.w), - SCDebounceWidget( - child: Container( - alignment: Alignment.center, - width: 120.w, - height: 35.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.w), - color: - (selecteTheme?.useTheme ?? false) - ? Colors.white - : SocialChatTheme.primaryLight, - ), - child: text( - (selecteTheme?.useTheme ?? false) - ? SCAppLocalizations.of(context)!.inUse - : SCAppLocalizations.of(context)!.use, - fontWeight: FontWeight.w600, - fontSize: 14.sp, - textColor: - !(selecteTheme?.useTheme ?? false) - ? Colors.white - : Color(0xffB1B1B1), - ), - ), - onTap: () { - if (!(selecteTheme?.useTheme ?? false)) { - ///佩戴 - SmartDialog.show( - tag: "showConfirmDialog", - alignment: Alignment.center, - debounce: true, - animationType: SmartAnimationType.fade, - builder: (_) { - return MsgDialog( - title: SCAppLocalizations.of(context)!.tips, - msg: - SCAppLocalizations.of( - context, - )!.confirmUseTips, - btnText: - SCAppLocalizations.of(context)!.confirm, - onEnsure: () { - _use(selecteTheme!, false); - }, - ); - }, - ); - } else { - ///卸下 - SmartDialog.show( - tag: "showConfirmDialog", - alignment: Alignment.center, - debounce: true, - animationType: SmartAnimationType.fade, - builder: (_) { - return MsgDialog( - title: SCAppLocalizations.of(context)!.tips, - msg: - SCAppLocalizations.of( - context, - )!.confirmUnUseTips, - btnText: - SCAppLocalizations.of(context)!.confirm, - onEnsure: () { - _use(selecteTheme!, true); - }, - ); - }, - ); - } - }, - ), - ], - ), - SizedBox(height: 10.w), - ], - ), - ) - : Container(), - ], - ), - ); - } - - @override - Widget buildItem(SCRoomThemeListRes res) { - return GestureDetector( - child: Container( - decoration: BoxDecoration( - color: Color(0xff18F2B1).withOpacity(0.1), - border: Border.all( - color: - selecteTheme?.id == res.id - ? SocialChatTheme.primaryLight - : Colors.transparent, - width: 1.w, - ), - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - child: Stack( - alignment: Alignment.center, - children: [ - Column( - children: [ - SizedBox(height: 25.w), - netImage( - url: res.themeBack ?? "", - width: 55.w, - height: 55.w, - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - SizedBox(height: 8.w), - ], - ), - PositionedDirectional( - top: 0, - start: 0, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 5.w), - decoration: BoxDecoration( - color: SocialChatTheme.primaryLight, - borderRadius: BorderRadiusDirectional.only( - topStart: Radius.circular(6.w), - topEnd: Radius.circular(12.w), - bottomEnd: Radius.circular(12.w), - ), - ), - child: Row( - children: [ - CountdownTimer( - fontSize: 9.sp, - color: Colors.white, - expiryDate: DateTime.fromMillisecondsSinceEpoch( - res.expireTime ?? 0, - ), - ), - SizedBox(width: 3.w), - Image.asset( - "sc_images/store/sc_icon_bag_clock.png", - width: 22.w, - height: 22.w, - ), - ], - ), - ), - ), - ], - ), - ), - onTap: () { - selecteTheme = res; - setState(() {}); - }, - ); - } - - ///加载数据 - @override - loadPage({ - required int page, - required Function(List) onSuccess, - Function? onErr, - }) async { - try { - var storeList = await SCStoreRepositoryImp().roomThemeBackpack(); - for (var v in storeList) { - if (v.useTheme ?? false) { - selecteTheme = v; - continue; - } - } - onSuccess(storeList); - } catch (e) { - if (onErr != null) { - onErr(); - } - } - } - - void _use(SCRoomThemeListRes res, bool unload) { - SCLoadingManager.show(); - SCStoreRepositoryImp() - .themeSwitchUse(res.id ?? "", unload) - .then((value) async { - SCLoadingManager.hide(); - SmartDialog.dismiss(tag: "showPropsDetail"); - if (!unload) { - SCTts.show(SCAppLocalizations.of(context)!.successfulWear); - Provider.of( - context!, - listen: false, - ).updateRoomBG(res); - } else { - Provider.of( - context!, - listen: false, - ).updateRoomBG(SCRoomThemeListRes()); - selecteTheme = null; - SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded); - } - loadData(1); - - ///发送一条消息更新远端房间信息 - String? groupId = - Provider.of( - context, - listen: false, - ).currenRoom?.roomProfile?.roomProfile?.roomAccount; - if (groupId != null) { - Msg msg = Msg( - groupId: groupId, - msg: unload ? "" : jsonEncode(res.toJson()), - type: SCRoomMsgType.roomBGUpdate, - ); - Provider.of( - context!, - listen: false, - ).dispatchMessage(msg, addLocal: false); - } - }) - .catchError((e) { - SCLoadingManager.hide(); - }); - } - - void _showDetail(SCRoomThemeListRes res) { - SmartDialog.show( - tag: "showPropsDetail", - alignment: Alignment.center, - animationType: SmartAnimationType.fade, - builder: (_) { - return PropsBagThemeDetailDialog(res, () { - loadData(1); - }); - }, - ); - } -} +import 'dart:convert'; +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/theme/socialchat_theme.dart'; +import 'package:yumi/ui_kit/components/sc_page_list.dart'; +import 'package:yumi/app/routes/sc_fluro_navigator.dart'; +import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart'; +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'; +import '../../../../services/audio/rtc_manager.dart'; +import '../../../../services/audio/rtm_manager.dart'; +import '../../../../ui_kit/components/sc_debounce_widget.dart'; +import '../../../../ui_kit/components/dialog/dialog_base.dart'; +import '../../../../ui_kit/components/sc_tts.dart'; +import '../../../../ui_kit/widgets/room/room_msg_item.dart'; + +///背包-房间主题 +class BagsThemePage extends SCPageList { + @override + _BagsThemePageState createState() => _BagsThemePageState(); +} + +class _BagsThemePageState + extends SCPageListState { + SCRoomThemeListRes? selecteTheme; + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.88, + ); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + Expanded( + child: + items.isEmpty && isLoading + ? const SCBagGridSkeleton(showTitle: false) + : buildList(context), + ), + selecteTheme != null ? SizedBox(height: 10.w) : Container(), + selecteTheme != null + ? Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + color: const Color(0xff18F2B1).withValues(alpha: 0.1), + ), + child: Column( + children: [ + SizedBox(height: 5.w), + Row( + spacing: 5.w, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + "${SCAppLocalizations.of(context)!.expirationTime}:", + textColor: Colors.white, + fontSize: 12.sp, + ), + CountdownTimer( + fontSize: 11.sp, + color: SocialChatTheme.primaryLight, + expiryDate: DateTime.fromMillisecondsSinceEpoch( + selecteTheme?.expireTime ?? 0, + ), + ), + ], + ), + SizedBox(height: 10.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SCDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 120.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: SocialChatTheme.primaryLight, + ), + child: text( + SCAppLocalizations.of(context)!.renewal, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: Colors.white, + ), + ), + onTap: () { + ///过期 续费操作 + SCNavigatorUtils.push( + context, + StoreRoute.list, + replace: false, + ); + }, + ), + SizedBox(width: 10.w), + SCDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 120.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: + (selecteTheme?.useTheme ?? false) + ? Colors.white + : SocialChatTheme.primaryLight, + ), + child: text( + (selecteTheme?.useTheme ?? false) + ? SCAppLocalizations.of(context)!.inUse + : SCAppLocalizations.of(context)!.use, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: + !(selecteTheme?.useTheme ?? false) + ? Colors.white + : Color(0xffB1B1B1), + ), + ), + onTap: () { + if (!(selecteTheme?.useTheme ?? false)) { + ///佩戴 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: SCAppLocalizations.of(context)!.tips, + msg: + SCAppLocalizations.of( + context, + )!.confirmUseTips, + btnText: + SCAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(selecteTheme!, false); + }, + ); + }, + ); + } else { + ///卸下 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: SCAppLocalizations.of(context)!.tips, + msg: + SCAppLocalizations.of( + context, + )!.confirmUnUseTips, + btnText: + SCAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(selecteTheme!, true); + }, + ); + }, + ); + } + }, + ), + ], + ), + SizedBox(height: 10.w), + ], + ), + ) + : Container(), + ], + ), + ); + } + + @override + Widget buildItem(SCRoomThemeListRes res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: const Color(0xff18F2B1).withValues(alpha: 0.1), + border: Border.all( + color: + selecteTheme?.id == res.id + ? SocialChatTheme.primaryLight + : Colors.transparent, + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + alignment: Alignment.center, + children: [ + Column( + children: [ + SizedBox(height: 25.w), + netImage( + url: res.themeBack ?? "", + width: 55.w, + height: 55.w, + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + SizedBox(height: 8.w), + ], + ), + PositionedDirectional( + top: 0, + start: 0, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5.w), + decoration: BoxDecoration( + color: SocialChatTheme.primaryLight, + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(6.w), + topEnd: Radius.circular(12.w), + bottomEnd: Radius.circular(12.w), + ), + ), + child: Row( + children: [ + CountdownTimer( + fontSize: 9.sp, + color: Colors.white, + expiryDate: DateTime.fromMillisecondsSinceEpoch( + res.expireTime ?? 0, + ), + ), + SizedBox(width: 3.w), + Image.asset( + "sc_images/store/sc_icon_bag_clock.png", + width: 22.w, + height: 22.w, + ), + ], + ), + ), + ), + ], + ), + ), + onTap: () { + selecteTheme = res; + setState(() {}); + }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var storeList = await SCStoreRepositoryImp().roomThemeBackpack(); + for (var v in storeList) { + if (v.useTheme ?? false) { + selecteTheme = v; + continue; + } + } + onSuccess(storeList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _use(SCRoomThemeListRes res, bool unload) { + SCLoadingManager.show(); + SCStoreRepositoryImp() + .themeSwitchUse(res.id ?? "", unload) + .then((value) async { + SCLoadingManager.hide(); + SmartDialog.dismiss(tag: "showPropsDetail"); + if (!unload) { + SCTts.show(SCAppLocalizations.of(context)!.successfulWear); + Provider.of( + context!, + listen: false, + ).updateRoomBG(res); + } else { + Provider.of( + context!, + listen: false, + ).updateRoomBG(SCRoomThemeListRes()); + selecteTheme = null; + SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded); + } + loadData(1); + + ///发送一条消息更新远端房间信息 + String? groupId = + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.roomAccount; + if (groupId != null) { + Msg msg = Msg( + groupId: groupId, + msg: unload ? "" : jsonEncode(res.toJson()), + type: SCRoomMsgType.roomBGUpdate, + ); + Provider.of( + context!, + listen: false, + ).dispatchMessage(msg, addLocal: false); + } + }) + .catchError((e) { + SCLoadingManager.hide(); + }); + } + + void _showDetail(SCRoomThemeListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsBagThemeDetailDialog(res, () { + loadData(1); + }); + }, + ); + } +} diff --git a/lib/services/general/sc_app_general_manager.dart b/lib/services/general/sc_app_general_manager.dart index cc7f15a..0a3c38f 100644 --- a/lib/services/general/sc_app_general_manager.dart +++ b/lib/services/general/sc_app_general_manager.dart @@ -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 countryModeList = []; - Map> _countryMap = {}; - Map _countryByNameMap = {}; + final Map> _countryMap = {}; + final Map _countryByNameMap = {}; bool _isFetchingCountryList = false; ///礼物 List giftResList = []; - Map _giftByIdMap = {}; + final Map _giftByIdMap = {}; + final Set _warmedGiftCoverUrls = {}; + final Set _warmingGiftCoverUrls = {}; + bool _isFetchingGiftList = false; + bool _hasGiftListLoaded = false; Map> giftByTab = {}; Map> activityGiftByTab = {}; @@ -38,6 +46,8 @@ class SCAppGeneralManager extends ChangeNotifier { // configLevel(); } + bool get isGiftListLoading => _isFetchingGiftList && !_hasGiftListLoaded; + ///加载国家列表 Future fetchCountryList() async { if (_isFetchingCountryList) { @@ -116,44 +126,122 @@ class SCAppGeneralManager extends ChangeNotifier { Scaffold.of(context).closeDrawer(); } - void giftList({bool includeCustomized = true}) async { + Future 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(); - giftByTab.clear(); - for (var gift in giftResList) { - giftByTab[gift.giftTab]; - var gmap = giftByTab[gift.giftTab]; - gmap ??= []; - gmap.add(gift); - giftByTab[gift.giftTab!] = gmap; - var gAllMap = giftByTab["ALL"]; - gAllMap ??= []; - if (gift.giftTab != "NSCIONAL_FLAG" && - gift.giftTab != "ACTIVITY" && - gift.giftTab != "LUCKY_GIFT" && - gift.giftTab != "CP" && - gift.giftTab != "CUSTOMIZED" && - gift.giftTab != "MAGIC") { - gAllMap.add(gift); - } - giftByTab["ALL"] = gAllMap; - _giftByIdMap[gift.id!] = gift; - } - - if (includeCustomized) { - giftByTab["CUSTOMIZED"] ??= []; - - ///定制礼物需要一个占位符,显示规则的 - giftByTab["CUSTOMIZED"]?.insert(0, SocialChatGiftRes(id: "-1000")); - } else { - giftByTab.remove("CUSTOMIZED"); - } - + _hasGiftListLoaded = true; + _rebuildGiftTabs(includeCustomized: includeCustomized); notifyListeners(); ///先去预下载 downLoad(giftResList); - } catch (e) {} + 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]; + gmap ??= []; + gmap.add(gift); + giftByTab[gift.giftTab!] = gmap; + var gAllMap = giftByTab["ALL"]; + gAllMap ??= []; + if (gift.giftTab != "NSCIONAL_FLAG" && + gift.giftTab != "ACTIVITY" && + gift.giftTab != "LUCKY_GIFT" && + gift.giftTab != "CP" && + gift.giftTab != "CUSTOMIZED" && + gift.giftTab != "MAGIC") { + gAllMap.add(gift); + } + giftByTab["ALL"] = gAllMap; + _giftByIdMap[gift.id!] = gift; + } + + if (includeCustomized) { + giftByTab["CUSTOMIZED"] ??= []; + + ///定制礼物需要一个占位符,显示规则的 + giftByTab["CUSTOMIZED"]?.insert(0, SocialChatGiftRes(id: "-1000")); + } else { + giftByTab.remove("CUSTOMIZED"); + } + } + + Future warmGiftPageCovers( + String type, { + required int pageIndex, + int pageCount = 1, + int pageSize = 8, + }) async { + final gifts = giftByTab[type] ?? []; + 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)); + } + + Future warmGiftCoverImages( + Iterable gifts, { + int maxConcurrency = 4, + }) async { + final pending = >[]; + 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 gameBanners = []; ///首页banner - void loadMainBanner() async { + Future loadMainBanner() async { try { var banners = await SCConfigRepositoryImp().getBanner(); homeBanners.clear(); @@ -227,7 +319,9 @@ class SCAppGeneralManager extends ChangeNotifier { } } notifyListeners(); - } catch (e) {} + } catch (_) { + return; + } } Map> emojiByTab = {}; @@ -249,10 +343,12 @@ class SCAppGeneralManager extends ChangeNotifier { SCBannerLeaderboardRes? appLeaderResult; ///查询榜单前三名 - void appLeaderboard() async { + Future appLeaderboard() async { try { appLeaderResult = await SCChatRoomRepository().appLeaderboard(); notifyListeners(); - } catch (e) {} + } catch (_) { + return; + } } } diff --git a/lib/services/room/rc_room_manager.dart b/lib/services/room/rc_room_manager.dart index f832211..1d7702a 100644 --- a/lib/services/room/rc_room_manager.dart +++ b/lib/services/room/rc_room_manager.dart @@ -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 { - myRoom = null; - myRoom = await SCAccountRepository().myProfile(); + Future fetchMyRoomData() async { + if (isMyRoomLoading) { + return; + } + isMyRoomLoading = true; + if (!hasLoadedMyRoom) { + myRoom = null; + } 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(); diff --git a/lib/shared/tools/sc_network_image_utils.dart b/lib/shared/tools/sc_network_image_utils.dart new file mode 100644 index 0000000..80606c2 --- /dev/null +++ b/lib/shared/tools/sc_network_image_utils.dart @@ -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? buildNetworkImageHeaders(String url) { + if (!_requiresAuthenticatedImageHeaders(url)) { + return null; + } + final uri = Uri.tryParse(url); + final headers = { + "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 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 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(); + 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; + }, + ); +} diff --git a/lib/ui_kit/components/sc_compontent.dart b/lib/ui_kit/components/sc_compontent.dart index b2356d6..fee53a7 100644 --- a/lib/ui_kit/components/sc_compontent.dart +++ b/lib/ui_kit/components/sc_compontent.dart @@ -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? _buildImageHeaders(String url) { - if (!_requiresAuthenticatedImageHeaders(url)) { - return null; - } - final uri = Uri.tryParse(url); - final headers = { - "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( diff --git a/lib/ui_kit/components/sc_float_ichart.dart b/lib/ui_kit/components/sc_float_ichart.dart index fccb3df..8e7adbe 100644 --- a/lib/ui_kit/components/sc_float_ichart.dart +++ b/lib/ui_kit/components/sc_float_ichart.dart @@ -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 { 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)), diff --git a/lib/ui_kit/widgets/room/room_live_audio_indicator.dart b/lib/ui_kit/widgets/room/room_live_audio_indicator.dart new file mode 100644 index 0000000..b0ce46e --- /dev/null +++ b/lib/ui_kit/widgets/room/room_live_audio_indicator.dart @@ -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 createState() => + _SCRoomLiveAudioIndicatorState(); +} + +class _SCRoomLiveAudioIndicatorState extends State + with SingleTickerProviderStateMixin { + static const List _phaseOffsets = [0.0, 0.18, 0.36]; + static const List _minFactors = [0.28, 0.2, 0.34]; + static const List _maxFactors = [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 phaseOffsets; + final List minFactors; + final List 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, + ); + } +} diff --git a/lib/ui_kit/widgets/room/room_online_user_widget.dart b/lib/ui_kit/widgets/room/room_online_user_widget.dart index e81f9a2..9b604ef 100644 --- a/lib/ui_kit/widgets/room/room_online_user_widget.dart +++ b/lib/ui_kit/widgets/room/room_online_user_widget.dart @@ -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 { "", width: 23.w, height: 23.w, + defaultImg: + SCGlobalConfig + .businessLogicStrategy + .getMePageDefaultAvatarImage(), shape: BoxShape.circle, border: Border.all( color: Colors.white, diff --git a/lib/ui_kit/widgets/store/store_bag_page_helpers.dart b/lib/ui_kit/widgets/store/store_bag_page_helpers.dart new file mode 100644 index 0000000..df7c0c8 --- /dev/null +++ b/lib/ui_kit/widgets/store/store_bag_page_helpers.dart @@ -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], + ), + ), + ); +} diff --git a/需求进度.md b/需求进度.md index 719a341..ecebb94 100644 --- a/需求进度.md +++ b/需求进度.md @@ -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。