import 'package:auto_size_text/auto_size_text.dart'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:keyboard/base/base_page.dart'; import 'package:keyboard/data/consts/payment_type.dart'; import 'package:keyboard/data/consts/web_url.dart'; import 'package:keyboard/module/store/store_controller.dart'; import 'package:keyboard/module/store/store_user_reviews_bean.dart'; import 'package:keyboard/resource/string.gen.dart'; import '../../resource/assets.gen.dart'; import '../../router/app_pages.dart'; import '../../utils/date_util.dart'; import '../../widget/horizontal_dashed_line.dart'; import '../../utils/styles.dart'; import '../../widget/click_text_span.dart'; class StorePage extends BasePage { const StorePage({super.key}); static start() { Get.toNamed(RoutePath.store); } @override backgroundColor() => const Color(0xFFFFF8D4); @override bool immersive() { return true; } @override Widget buildBody(BuildContext context) { return Stack( children: [ SingleChildScrollView( child: Column( children: [ _buildBanner(context), SizedBox(height: 12.h), _buildGoodsCard(), _buildVipDesc(), SizedBox(height: 32.h), _buildUserReviews(), SizedBox(height: 20.h), _buildUserNotice(), ], ), ), Positioned(top: 0, left: 0, right: 0, child: _buildTitle()), Positioned(bottom: 0, left: 0, right: 0, child: _buildBuyButtonCard()), ], ); } _buildTitle() { return SafeArea( child: Container( alignment: Alignment.centerLeft, padding: EdgeInsets.only(top: 16.h, left: 16.w, right: 16.w), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ GestureDetector( onTap: controller.clickBack, child: Assets.images.iconStoreBack.image( width: 32.w, height: 32.w, ), ), Obx(() { return SizedBox( height: 32.h, child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ // 会员状态信息 _buildMemberInfo(), SizedBox(width: 8.w), controller.isLogin ? Assets.images.iconMineUserLogged.image( width: 28.w, height: 28.w, ) : Assets.images.iconMineUserNoLogin.image( width: 28.w, height: 28.w, ), ], ), ); }), ], ), ), ); } // 会员信息 Widget _buildMemberInfo() { return Container( height: 32.h, alignment: Alignment.center, padding: EdgeInsets.symmetric(horizontal: 12.w), decoration: BoxDecoration( color: Colors.black.withAlpha(77), borderRadius: BorderRadius.circular(21.r), ), child: Text.rich( TextSpan(children: _getMemberStatusText()), textAlign: TextAlign.right, ), ); } // 会员状态文字逻辑提取 List _getMemberStatusText() { // 未登录 if (!controller.isLogin) { return [ TextSpan( text: StringName.memberNoLogged, style: _vipTextStyle(isHighlight: true), ), TextSpan(text: StringName.memberCardNoVipDesc, style: _vipTextStyle()), ]; } final isMember = controller.memberStatusInfo?.isMember ?? false; final isPermanent = controller.memberStatusInfo?.permanent ?? false; final username = controller.userInfo?.name ?? ''; if (isMember) { if (isPermanent) { return [ TextSpan( text: StringName.memberCardPermanentVipDesc1, style: _vipTextStyle(), ), TextSpan( text: StringName.memberCardPermanentVipDesc2, style: _vipTextStyle(isHighlight: true), ), ]; } else { return [ TextSpan(text: StringName.memberVipDesc, style: _vipTextStyle()), TextSpan( text: DateUtil.fromMillisecondsSinceEpoch( 'yyyy.MM.dd', controller.memberStatusInfo?.endTimestamp ?? 0, ), style: _vipTextStyle(isHighlight: true), ), ]; } } // 登录但不是会员 return [ TextSpan( text: controller.userInfo?.name ?? controller.getUserName(), style: _vipTextStyle(isHighlight: true), ), TextSpan(text: StringName.memberCardNoVipDesc, style: _vipTextStyle()), ]; } // 统一的会员状态文本样式 TextStyle _vipTextStyle({bool isHighlight = false}) { return TextStyle( color: isHighlight ? const Color(0xFFFFD273) : Colors.white, fontSize: 12.sp, fontWeight: FontWeight.w400, ); } Widget _buildGoodsCard() { return Container( margin: EdgeInsets.symmetric(horizontal: 16.w), padding: EdgeInsets.only( top: 16.h, left: 16.w, right: 16.w, bottom: 24.h, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(16.r), topRight: Radius.circular(16.r), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Assets.images.iconGoodsInfoTitle.image( width: 115.w, fit: BoxFit.cover, ), SizedBox(height: 16.h), Obx(() { return Column( children: controller.filteredGoodsList.map((item) { return Obx(() { return GestureDetector( onTap: () => controller.onGoodsItemClick(item), child: _buildGoodsItem( item, controller.selectedGoodsInfoItem?.id == item.id, ), ); }); }).toList(), ); }), _buildPayWayCard(), ], ), ); } Widget _buildPayWayCard() { return GestureDetector( onTap: () => controller.clickPayWaySwitch(), child: Container( height: 36.h, padding: EdgeInsets.symmetric(horizontal: 10.w), decoration: ShapeDecoration( shape: RoundedRectangleBorder( side: BorderSide(width: 1, color: const Color(0xFFECEBE0)), borderRadius: BorderRadius.circular(10.r), ), ), child: Row( children: [ Text( StringName.storePayWay, style: Styles.getTextStyleBlack204W400(14.sp), ), const Spacer(), Obx(() { if (controller.selectedPayWay == null) { return SizedBox.shrink(); } return Row( children: [ Image.asset( getPaymentIconPath( payMethod: controller.selectedPayWay!.payMethod, payPlatform: controller.selectedPayWay!.payPlatform, ), width: 20.w, height: 20.w, ), SizedBox(width: 4.w), Text( controller.selectedPayWay?.title ?? '', style: Styles.getTextStyleBlack204W400(14.sp), ), SizedBox(width: 6.w), Assets.images.iconStoreSwitchPay.image( width: 20.w, height: 20.w, fit: BoxFit.fill, ), ], ); }), ], ), ), ); } Widget _buildGoodsItem(item, bool isSelected) { return Container( margin: EdgeInsets.only(bottom: 8.h), width: 296.w, height: 70.h, decoration: ShapeDecoration( gradient: LinearGradient( begin: Alignment(0.77, -0.00), end: Alignment(0.77, 1.00), colors: isSelected ? [const Color(0xFFFF9416), const Color(0xFFFF7813)] : [const Color(0xFFFEE057), const Color(0xFFFFC400)], ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.r), ), ), child: Row( children: [ Container( width: 212.w, height: 70.h, decoration: ShapeDecoration( gradient: isSelected ? LinearGradient( begin: Alignment(-0.06, 0.50), end: Alignment(1.14, 0.50), colors: [ const Color(0xFFFFF895), const Color(0xFFFFE941), ], ) : null, color: isSelected ? null : const Color(0xFFFFFDEE), shape: RoundedRectangleBorder( side: BorderSide(width: 1, color: const Color(0xFFFEE86B)), borderRadius: BorderRadius.circular(isSelected ? 8 : 10.r), ), ), child: Stack( children: [ if (isSelected) IgnorePointer( child: Assets.images.bgStoreSelectedItem.image( width: 212.w, height: 70.h, fit: BoxFit.fill, ), ), Padding( padding: EdgeInsets.only(left: 16.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Row( children: [ RichText( text: TextSpan( children: [ TextSpan( text: '¥', style: Styles.getTextStyleFF663300W700(14.sp), ), TextSpan( text: item.priceDescNumber, style: Styles.getTextStyleFF663300W700(18.sp), ), TextSpan( text: item.priceDescUnit, style: Styles.getTextStyleFF663300W400(13.sp), ), ], ), ), if (item.mostDesc?.isNotEmpty == true) Container( constraints: BoxConstraints( minHeight: 20.w, maxHeight: 20.w, minWidth: 40.w, maxWidth: 120.w, ), padding: EdgeInsets.only( left: 16.w, top: 2.h, bottom: 2.h, right: 4.w, ), decoration: BoxDecoration( image: DecorationImage( image: Assets.images.iconStoreMost.provider(), fit: BoxFit.fill, alignment: Alignment.bottomLeft, ), ), child: AutoSizeText( item.mostDesc!, style: TextStyle( color: Colors.white, fontSize: 12.sp, fontWeight: FontWeight.w500, letterSpacing: -0.60, ), maxLines: 1, overflow: TextOverflow.ellipsis, minFontSize: 8, // 最小字体 stepGranularity: 0.5, // 缩小步长,越小越丝滑 ), ), ], ), Text( item.description!, style: Styles.getTextStyle99673300W400(12.sp), ), ], ), ), ], ), ), // 右侧 Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( item.name, style: isSelected ? Styles.getTextStyleFFECBBW500(15.sp) : Styles.getTextStyleFF663300W500(15.sp), ), Container( padding: EdgeInsets.symmetric(horizontal: 8.w), decoration: ShapeDecoration( color: isSelected ? const Color(0xFFFFECBB) : null, gradient: isSelected ? null : LinearGradient( begin: Alignment(0.77, -0.00), end: Alignment(0.77, 1.00), colors: [ const Color(0xFFFF9416), const Color(0xFFFF7813), ], ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( isSelected ? 17.r : 10.r, ), ), ), child: Text( '¥${item.amountText}', textAlign: TextAlign.center, style: isSelected ? Styles.getTextStyleFF7F14W500(12.sp) : Styles.getTextStyleWhiteW500(12.sp), ), ), ], ), ), ], ), ); } Widget _buildVipDesc() { return Container( alignment: Alignment.centerLeft, margin: EdgeInsets.symmetric(horizontal: 16.w), padding: EdgeInsets.symmetric(horizontal: 16.w), width: double.infinity, height: 36.h, decoration: ShapeDecoration( gradient: LinearGradient( begin: Alignment(0.00, 0.50), end: Alignment(1.00, 0.50), colors: [const Color(0x7FFFF3A3), const Color(0x21FFF3A3)], ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(20.r), bottomRight: Radius.circular(20.r), ), ), shadows: [ BoxShadow( color: Colors.black.withAlpha(10), blurRadius: 10.r, spreadRadius: 0.r, offset: Offset(0, 8.r), ), ], ), child: Obx(() { return Text( controller.selectedGoodsInfoItem?.selectDesc ?? "", style: Styles.getTextStyle99673300W400(12.sp), ); }), ); } // 轮播图 Widget _buildBanner(BuildContext context) { return SizedBox( width: double.infinity, child: Stack( children: [ CarouselSlider( carouselController: controller.carouselSliderController, options: CarouselOptions( height: 280.h, viewportFraction: 1, initialPage: 0, enableInfiniteScroll: true, reverse: false, autoPlay: true, autoPlayInterval: const Duration(seconds: 3), autoPlayAnimationDuration: const Duration(milliseconds: 800), autoPlayCurve: Curves.fastOutSlowIn, scrollDirection: Axis.horizontal, onPageChanged: (index, reason) { controller.onBannerChanged(index, reason); }, ), items: controller.bannerList.map((item) { return item.banner.image( width: double.infinity, fit: BoxFit.cover, ); }).toList(), ), Positioned(bottom: 0, left: 0, right: 0, child: _buildIndicator()), ], ), ); } // 指示器 _buildIndicator() { return Container( height: 36.h, margin: EdgeInsets.symmetric(horizontal: 16.w), decoration: ShapeDecoration( color: const Color(0xE5121212), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(21.r), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: controller.bannerList.asMap().entries.map((entry) { return Obx(() { final isSelectedBanner = controller.currentBannerIndex == entry.key; return Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ GestureDetector( onTap: () => controller.carouselSliderController .animateToPage(entry.key), child: SizedBox( width: 100.w, child: Stack( alignment: Alignment.center, clipBehavior: Clip.none, children: [ if (isSelectedBanner) Positioned( top: -8.h, child: controller .bannerList[entry.key] .indicatorImg .image( width: 100.w, height: 40.h, fit: BoxFit.fill, ), ) else Text( controller.bannerList[entry.key].unSelectedDesc, style: Styles.getTextStyleWhiteW400(14.sp), ), ], ), ), ), if (entry.key != controller.bannerList.length - 1) Padding( padding: EdgeInsets.only(left: 4.w), child: Assets.images.iconStoreDivider.image( width: 2.w, height: 17.h, fit: BoxFit.fill, ), ), ], ); }); }).toList(), ), ); } Widget _buildUserReviews() { return Container( width: double.infinity, child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Assets.images.iconStoreUserReviewsTitle.image( width: 240.w, fit: BoxFit.cover, ), SizedBox(height: 16.h), Container( width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(19.r), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.white, Color(0xfffff8d4)], ), ), child: Column( children: [ SizedBox(height: 20.h), Container( width: 326.w, decoration: BoxDecoration( borderRadius: BorderRadius.circular(19.r), image: DecorationImage( alignment: Alignment.topCenter, image: Assets.images.bgStoreUserReviews.provider(), fit: BoxFit.contain, ), ), child: Column( children: [ Container( padding: EdgeInsets.only(left: 16.w), alignment: Alignment.centerLeft, height: 39.h, child: Assets.images.iconStoreUserReviewsLogo.image( width: 92.w, height: 24.h, ), ), Container( width: 326.w, decoration: ShapeDecoration( color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.r), ), shadows: [ BoxShadow( color: Colors.black.withAlpha(10), blurRadius: 10.r, spreadRadius: 0.r, offset: Offset(0, 5.r), ), ], ), child: Column( children: controller.userReviewsList.map((item) { return _buildReviewsItem(item); }).toList(), ), ), ], ), ), ], ), ), ], ), ); } Widget _buildReviewsItem(StoreUserReviewsBean item) { return Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 12.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ item.avatar.image(width: 28.w, height: 28.h, fit: BoxFit.cover), SizedBox(width: 10.w), Text( item.userName, style: Styles.getTextStyleBlack204W500(14.sp), ), SizedBox(width: 6.w), Assets.images.iconStorePermanentMember.image( width: 70.w, height: 20.h, fit: BoxFit.cover, ), ], ), SizedBox(height: 4.h), Padding( padding: EdgeInsets.only(left: 38.w), child: Text( item.userReviews, style: TextStyle( color: Colors.black.withAlpha(153), fontSize: 12.sp, fontWeight: FontWeight.w400, ), ), ), SizedBox(height: 12.h), if (controller.userReviewsList.indexOf(item) != controller.userReviewsList.length - 1) HorizontalDashedLine( width: 304.w, color: const Color(0XFFEDEBE1), strokeWidth: 2.h, dashLength: 4.w, dashSpace: 4.w, ), ], ), ); } // 用户须知 Widget _buildUserNotice() { return Container( margin: EdgeInsets.symmetric(horizontal: 16.w), decoration: BoxDecoration(borderRadius: BorderRadius.circular(16.r)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('购买须知', style: Styles.getTextStyleFF663300W400(12.sp)), SizedBox(height: 8.h), Text( "1.会员权益将在购买成功后自动生效,如遇延迟,请耐心等待5-10分钟。若长时间未生效,请联系客服处理;\n" "2.本服务为虚拟商品,一经购买成功即视为使用,恕不支持退款或转让,感谢您的理解;\n" "3.您的支持是我们持续优化与服务的动力,我们将竭诚为您提供更好的体验;\n" "4.相关细则以《会员服务协议》为准,在法律允许范围内,我们保留最终解释权。开通即视为同意协议条款,请谨慎购买。\n" "如有疑问,欢迎随时咨询在线客服!", style: Styles.getTextStyle99673300W400(10.sp), ), SizedBox(height: 150.h), ], ), ); } Widget _buildBuyButtonCard() { return Container( width: 360.w, padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 11.h), decoration: ShapeDecoration( color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r), ), ), shadows: [ BoxShadow( color: Color(0x99FFE498), blurRadius: 20, offset: Offset(0, 0), spreadRadius: 0, ), ], ), child: Column( children: [ GestureDetector( onTap: controller.clickPayNow, child: Container( alignment: Alignment.topCenter, width: 328.w, height: 56.w, decoration: ShapeDecoration( color: const Color(0xFFFFF587), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30.55.r), ), ), child: Container( width: 328.w, height: 54.w, decoration: ShapeDecoration( gradient: LinearGradient( begin: Alignment(0.60, -0.39), end: Alignment(0.60, 0.95), colors: [ const Color(0xFFF95FAC), const Color(0xFFFD5E4D), const Color(0xFFFD5E4D), const Color(0xFFFB8A3C), ], ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30.55.r), ), ), child: Center( child: Text( StringName.storePayNow, style: Styles.getTextStyleWhiteW500(17.sp), ), ), ), ), ), _buildPrivacy(), ], ), ); } Widget _buildPrivacy() { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Obx(() { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { controller.isAgree.value = !controller.isAgree.value; }, child: Padding( padding: EdgeInsets.symmetric(vertical: 10.w, horizontal: 10.w), child: controller.isAgree.value ? Assets.images.iconStoreAgreePrivacy.image( width: 16.w, height: 16.w, ) : SizedBox( child: Container( padding: EdgeInsets.all(1.w), width: 16.w, height: 16.w, child: Container( decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: Colors.black.withAlpha(153), width: 1.w, ), ), ), ), ), ), ); }), Transform.translate( offset: Offset(-10.w, 0), child: Text.rich( TextSpan( children: [ TextSpan( text: StringName.textSpanIHaveReadAndAgree, style: TextStyle( color: Colors.black.withAlpha(153), fontSize: 10.sp, fontWeight: FontWeight.w400, ), ), ClickTextSpan( text: StringName.textSpanPrivacyPolicy, url: WebUrl.privacyPolicy, ), ClickTextSpan( text: StringName.textSpanServiceTerms, url: WebUrl.serviceAgreement, ), TextSpan( text: StringName.textSpanAnd, style: TextStyle( color: Colors.black.withAlpha(153), fontSize: 10.sp, fontWeight: FontWeight.w400, ), ), ClickTextSpan( text: StringName.textSpanMembershipAgreement, url: WebUrl.memberServiceAgreement, ), ], ), ), ), ], ); } }