import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/framework.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:get/get_core/src/get_main.dart'; import 'package:location/base/base_page.dart'; import 'package:location/module/member/activity/member_activity_page.dart'; import 'package:location/module/member/member_controller.dart'; import 'package:location/resource/assets.gen.dart'; import 'package:location/resource/colors.gen.dart'; import 'package:location/utils/common_expand.dart'; import 'package:location/utils/project_expand.dart'; import 'package:location/widget/auto_scroll_list_view.dart'; import '../../data/bean/goods_bean.dart'; import '../../data/bean/goods_evaluate_info.dart'; import '../../data/bean/member_status_info.dart'; import '../../data/bean/pay_item_bean.dart'; import '../../data/consts/payment_type.dart'; import '../../resource/string.gen.dart'; import '../../router/app_pages.dart'; import '../../utils/date_util.dart'; import '../../widget/activity_countdown_txt_view.dart'; import '../../widget/animated_switcher_widget.dart'; import '../../widget/shimmer_effect.dart'; import 'member_discount_countdown_widget.dart'; import 'member_header_cycle_widget.dart'; ///进入会员类型 enum MemberPageType { ///通用进入 universalAccessEnter, ///倒计时试用完会员进入 afeterTrialMemberEnter, ///加好友进入 addFriendToEnter, //活动页 activity, } class MemberPage extends BasePage { MemberPage({super.key, this.pageType}); late MemberPageType? pageType = MemberPageType.universalAccessEnter; static Future start( {MemberPageType? enterTyp = MemberPageType.universalAccessEnter}) async { if (enterTyp == MemberPageType.activity) { return await MemberActivityPage.start() == true; } else { return await Get.toNamed(RoutePath.member, arguments: enterTyp) == true; } } @override bool immersive() { return true; } @override bool statusBarDarkFont() { return true; } @override Widget buildBody(BuildContext context) { return PopScope( canPop: !Platform.isAndroid, onPopInvokedWithResult: (bool didPop, dynamic result) async { if (didPop && Platform.isAndroid) { return; } controller.onPopBack(iosIsBack: false); }, child: Stack( children: [buildScrollView(), buildHeadBar(), buildMemberBottomView()], ), ); } Widget buildScrollView() { return SingleChildScrollView( physics: const ClampingScrollPhysics(), controller: controller.scrollController, child: Stack( children: [ MemberHeaderCycleWidget(), Column( children: [ SizedBox( height: 249.w + MediaQuery.of(Get.context!).padding.top - 24.w), Container( decoration: BoxDecoration( color: ColorName.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(20.w), topRight: Radius.circular(20.w)), boxShadow: [ BoxShadow( color: Color.fromRGBO(0, 63, 127, 0.10), blurRadius: 28.6, offset: Offset(0, -20), ), ], ), child: Stack( children: [ Container( margin: EdgeInsets.only(left: 2.w, right: 2.w, top: 2.w), decoration: BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(20.w), topRight: Radius.circular(20.w)), image: DecorationImage( image: Assets.images.iconMemberVipMiddleBg .provider(), )), width: double.infinity, height: 174.w, ), Container( //width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(20.w), topRight: Radius.circular(20.w)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 15.w), buildUserInfoView(), SizedBox(height: 12.w), buildGoodsList(), SizedBox(height: 12.w), buildPayWayView(), SizedBox(height: 12.w), Padding( padding: EdgeInsets.only(left: 16.w), child: Text(StringName.memberUserEvaluate, style: TextStyle( fontSize: 16.sp, color: ColorName.black, fontWeight: FontWeight.bold)), ), //SizedBox(height: 8.w), buildUserEvaluateList(), Obx(() { return Visibility( visible: controller.isShowMemberSubscribeTxt && Platform.isIOS, child: Padding( padding: EdgeInsets.only( left: 16.w, right: 16.w, top: 16.w), child: Text( StringName.memberIosPayDesc, style: TextStyle( fontSize: 10.sp, color: ColorName.black60), ), )); }), SizedBox( height: 190.w + MediaQuery.of(Get.context!).padding.bottom) ], ), ), ], ), ) ], ), ], )); } Widget buildGoodsList() { return Obx(() { final goods = controller.goodsList; final itemCount = goods.length; if (itemCount == 0) { return Container(); } final widgets = []; // 添加第一个商品(特殊样式) if (itemCount >= 1) { widgets.add(_createSpecialProduct(goods[0])); } // 如果有多个商品,添加普通样式商品 if (itemCount >= 2) { widgets.add(SizedBox(height: 7.w)); final rowItems = []; // 计算可显示的普通商品数量(最多2个) final ordinaryCount = itemCount >= 3 ? 2 : 1; for (int i = 1; i <= ordinaryCount + 1 && i < itemCount; i++) { rowItems.add(Expanded(child: _ordinaryProductWidget(goods[i]))); // 为除最后一个商品外的每个商品添加间距 if (i < ordinaryCount + 1 && i < itemCount - 1) { rowItems.add(SizedBox(width: 8)); } } widgets.add(Container( margin: EdgeInsets.only(left: 16.w, right: 16.w), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: rowItems, ), )); } return Column( children: widgets, ); }); } //特惠产品 Widget _createSpecialProduct(GoodsBean goodsInfo) { bool isSelected = controller.selectedGoods?.id == goodsInfo.id; return GestureDetector( onTap: () { controller.onGoodsItemClick(goodsInfo); }, child: Container( height: 96.w, margin: EdgeInsets.only(left: 12.w, right: 16.w), padding: EdgeInsets.only(left: 17.w), decoration: BoxDecoration( image: DecorationImage( image: isSelected ? Assets.images.iconMemberSpecialProductsSelect.provider() : Assets.images.iconMemberSpecialProductsNormal.provider(), fit: BoxFit.fill, ), ), child: Column( children: [ SizedBox( height: 29.w, ), Row( children: [ Column( children: [ RichText( text: TextSpan( style: TextStyle( color: isSelected ? '#FF5656'.color : "#323133".color, fontWeight: FontWeight.bold), children: [ TextSpan( text: '¥', style: TextStyle(fontSize: 16.sp, height: 1)), TextSpan( text: goodsInfo.amount.divideBy100(), style: TextStyle( fontSize: 28.sp, height: 1, //fontFamily: FontFamily.oppoSans )) ])), Text('¥${goodsInfo.originalAmount.divideBy100()}', style: TextStyle( decoration: TextDecoration.lineThrough, decorationColor: ColorName.black40, decorationThickness: 1.0, fontSize: 12.sp, color: ColorName.black40)) ], ), SizedBox( width: 22.24.w, ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( goodsInfo.name, style: TextStyle( fontSize: 17.sp, color: "#333333".color, fontWeight: FontWeight.bold), ), SizedBox( height: 3.w, ), Text( goodsInfo.description ?? "", style: TextStyle( fontSize: 11.sp, color: "#9191BA".color, fontWeight: FontWeight.bold), ), ], ), ], ), ], ), ), ); } ///普通产品 Widget _ordinaryProductWidget(GoodsBean goodsInfo) { bool isSelected = controller.selectedGoods?.id == goodsInfo.id; return GestureDetector( onTap: () { controller.onGoodsItemClick(goodsInfo); }, child: SizedBox( width: double.infinity, height: 62.w, child: Stack( children: [ Container( width: double.infinity, height: double.infinity, decoration: BoxDecoration( gradient: LinearGradient( colors: ['#FFFFFF'.color, '#F3EAFF'.color], begin: Alignment.topLeft, end: Alignment.bottomRight, stops: const [0.4, 1.0]), borderRadius: BorderRadius.circular(12.w), border: Border.all( color: isSelected ? '#7A13C6'.color : '#F1E6FF'.color, width: isSelected ? 2.5.w : 1.w, ))), Container( padding: EdgeInsets.only(left: 16.w, right: 12.w), child: Column( children: [ SizedBox( height: 15.w, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( goodsInfo.name, style: TextStyle( fontSize: 12.sp, color: "#333333".color, fontWeight: FontWeight.bold), ), //SizedBox(height: 6.w,), Text( goodsInfo.description ?? "", style: TextStyle( fontSize: 10.sp, color: ColorName.black40, fontWeight: FontWeight.bold), ), ], ), //SizedBox(width: 22.24.w,), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ RichText( text: TextSpan( style: TextStyle( color: isSelected ? '#FF5656'.color : "#323133".color, fontWeight: FontWeight.bold), children: [ TextSpan( text: '¥', style: TextStyle( color: isSelected ? "#FF5656".color : ColorName.black80, fontSize: 11.sp, height: 1)), TextSpan( text: goodsInfo.amount.divideBy100(), style: TextStyle( fontSize: 20.sp, height: 1, )) ])), Text('¥${goodsInfo.originalAmount.divideBy100()}', style: TextStyle( decoration: TextDecoration.lineThrough, decorationColor: ColorName.black30, decorationThickness: 1.0, fontSize: 10.sp, color: ColorName.black30)) ], ), ], ), ], ), ) ], ), ), ); } Widget buildMemberBottomView() { return Obx(() { if (controller.memberStatusInfo != null && controller.memberStatusInfo?.permanent == true) { return SizedBox(); // 不显示 } return Align( alignment: Alignment.bottomCenter, child: Stack( children: [ IgnorePointer( ignoring: true, // 不处理事件 child: Container( height: 190.w + MediaQuery.of(Get.context!).padding.bottom, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [ Colors.white, "#00FFFFFF".color, ], stops: [0.4368, 1.0], ), ), ), ), Positioned( left: 0, right: 0, bottom: MediaQuery.of(Get.context!).padding.bottom, child: IntrinsicHeight( child: Column( children: [ Stack( children: [ Container( width: 336.w, height: 47.w, decoration: BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(30.w), topRight: Radius.circular(30.w), ), color: '#FFFED8'.color), child: Align( alignment: Alignment(0.0, -0.75), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( StringName.memberActivityCountdown, style: TextStyle( fontSize: 11.sp, color: '#FF5656'.color), ), SizedBox(width: 4.w), Obx(() { return ActivityCountdownTextView( timeItemHeight: 15.w, contentPadding: EdgeInsets.zero, timeItemWidth: 16.w, textStyle: TextStyle( fontSize: 10.sp, color: Colors.white), duration: controller.activityDuration ?? Duration(seconds: 0), separator: buildCountdownSeparator(), timeBgBoxDecoration: BoxDecoration( color: '#FF5656'.color, borderRadius: BorderRadius.circular(3.w), )); }), SizedBox(width: 4.w), Text( StringName .memberActivitySpeciallyPreferential, style: TextStyle( fontSize: 10.sp, color: '#FF5656'.color), ) ], ), ), ), GestureDetector( onTap: controller.onBuyClick, child: Container( margin: EdgeInsets.only(top: 24.w), height: 50.w, width: 336.w, child: ShimmerEffect( image: Assets.images.imgMemberBtnShadow.provider(), shimmerWidthFactor: 0.244047619047619, duration: Duration(milliseconds: 3000), delay: Duration(milliseconds: 800), child: Container( height: 50.w, width: 336.w, alignment: Alignment.center, decoration: BoxDecoration( image: DecorationImage( image: Assets .images.iconMemberSettlementBg .provider(), fit: BoxFit.fill)), child: Text( StringName.memberVipUnlock, style: TextStyle( fontSize: 18.sp, color: '#FFF8EF'.color, fontWeight: FontWeight.bold), ), ), ), ), ) ], ), SizedBox(height: 8.w), buildPrivacyPolicyView(), SizedBox(height: 12.w), ], ), ), ) ], )); }); } Widget buildCountdownSeparator() { return Container( margin: EdgeInsets.symmetric(horizontal: 2.w), child: IntrinsicHeight( child: Column( children: [ Container( width: 2.w, height: 2.w, decoration: BoxDecoration( color: '#FF5656'.color, shape: BoxShape.circle, ), ), SizedBox(height: 3.w), Container( width: 2.w, height: 2.w, decoration: BoxDecoration( color: '#FF5656'.color, shape: BoxShape.circle, ), ) ], ), ), ); } Widget buildUserInfoView() { return Row( children: [ SizedBox(width: 18.w), controller.isLogin ? ClipOval( child: Container( width: 32.w, height: 32.w, child: CachedNetworkImage( imageUrl: controller.memberStatusInfo?.avatar ?? "", fit: BoxFit.cover, ), ), ) : Assets.images.iconMemberAvatar.image(width: 32.w, height: 32.w), SizedBox(width: 7.w), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Obx(() { return GestureDetector( onTap: controller.onLoginClick, child: Text( controller.phone?.isNotEmpty == true ? controller.getUserName(controller.phone!) : StringName.mineAccountGoLogin, style: TextStyle( fontSize: 13.sp, color: "#333333".color, fontWeight: FontWeight.bold)), ); }), Container( height: 16.w, width: MediaQuery.of(Get.context!).size.width - 77.w, child: Row( // 主轴默认左对齐,通过Spacer推挤右侧内容 children: [ // 左侧内容:会员等级描述 + VIP卡片描述 Visibility( visible: MemberStatusInfo.getLevelDesc( controller.memberStatusInfo) .isNotEmpty, child: Text( MemberStatusInfo.getLevelDesc( controller.memberStatusInfo), style: TextStyle( fontSize: 11.sp, fontWeight: FontWeight.w700, color: '#9144F8'.color)), ), buildMemberCardVipDesc(), Spacer(), ], ), ) ], ), SizedBox(width: 20.w) ], ); } Widget buildMemberCardVipDesc() { return Obx(() { String desc = ''; if (!controller.isLogin) { if ((controller.memberStatusInfo?.endTimestamp ?? 0) > 0) { desc = '${DateUtil.fromMillisecondsSinceEpoch('yyyy-MM-dd', controller.memberStatusInfo?.endTimestamp ?? 0)} ${StringName.memberCardExpirationDesc}'; } else { desc = StringName.memberCardNoLoginDesc; } } else if (controller.memberStatusInfo == null || controller.memberStatusInfo?.expired == true) { desc = StringName.memberCardNoVipDesc; } else if (controller.memberStatusInfo?.expired == false && controller.memberStatusInfo?.permanent == true) { desc = StringName.memberCardPermanentVipDesc; } else { desc = '${DateUtil.fromMillisecondsSinceEpoch('yyyy-MM-dd', controller.memberStatusInfo?.endTimestamp ?? 0)} ${StringName.memberCardExpirationDesc}'; } return Text(desc, style: TextStyle( fontSize: 11.sp, color: ColorName.black50, fontWeight: FontWeight.w400)); }); } Widget buildHeadBar() { return Column( children: [ SizedBox( height: MediaQuery.of(Get.context!).padding.top, ), SizedBox( width: double.infinity, height: 56.w, child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox( width: 15.w, ), GestureDetector( onTap: () => controller.onPopBack(), child: Assets.images.iconMemberVipBack .image(width: 26.w, height: 26..w), ), SizedBox( width: 10.w, ), Container( width: MediaQuery.of(Get.context!).size.width - 51.w, padding: EdgeInsets.only(right: 12.w), child: buildVerticalSlideshowWidget()), ], ), ) ], ); } Widget buildVerticalSlideshowWidget() { return Row( children: [ Expanded( child: Center( child: Container( width: 192.w, height: 26.w, decoration: BoxDecoration( color: ColorName.black40, borderRadius: BorderRadius.circular(87.w), ), child: Center( child: AnimatedSwitcherWidget( controller: controller.switcherController)), ), ), ), if (Platform.isAndroid) SizedBox( width: 40.w, ), if (Platform.isIOS) Obx(() { return Visibility( visible: Platform.isIOS && controller.accountRepository.isLogin.value, child: Row( children: [ SizedBox( width: 26.w, ), GestureDetector( onTap: controller.clickRecoverSubscribe, child: Container( height: 26.w, decoration: BoxDecoration( color: ColorName.black40, borderRadius: BorderRadius.circular(26.w / 2.0), ), padding: EdgeInsets.symmetric(horizontal: 10.w), child: Row( children: [ Assets.images.iconAppleRecoverSubscribe .image(width: 14.w, height: 14.w), Text(StringName.appleRecoverSubscribeTxt, style: TextStyle( fontSize: 11.sp, color: ColorName.white, fontWeight: FontWeight.w500)), ], ), ), ) ], )); }) ], ); } Widget buildPrivacyPolicyView() { return Padding( padding: EdgeInsets.only(left: 12.w), child: RichText( text: TextSpan( style: TextStyle(fontSize: 12.sp, color: ColorName.black40), children: [ TextSpan(text: '购买前请先阅读'), TextSpan( recognizer: TapGestureRecognizer() ..onTap = () { controller.onPrivacyPolicyClick(); }, text: '隐私政策', style: TextStyle( color: ColorName.black60, decoration: TextDecoration.underline)), TextSpan(text: '&'), TextSpan( recognizer: TapGestureRecognizer() ..onTap = () { controller.onTermOfServiceClick(); }, text: '服务条款', style: TextStyle( color: ColorName.black60, decoration: TextDecoration.underline)), if (Platform.isIOS) TextSpan(text: '&'), if (Platform.isIOS) TextSpan( recognizer: TapGestureRecognizer() ..onTap = () { controller.onRenewalAgreementClick(); }, text: '会员协议', style: TextStyle( color: ColorName.black60, decoration: TextDecoration.underline)), ])), ); } Widget buildFunctionList() { return SizedBox( height: 80.w, child: AutoScrollListView( padding: EdgeInsets.only(left: 12.w), itemBuilder: (ctx, index) { final item = controller.funList[index]; return Padding( padding: EdgeInsets.only(right: 20.w), child: Column( children: [ Image.asset(item.iconPath, width: 36.w, height: 36.w), Spacer(flex: 3), Text(item.funName, style: TextStyle( fontSize: 12.8.sp, color: ColorName.black90, fontWeight: FontWeight.bold)), Spacer(flex: 2), Text(item.funDesc, style: TextStyle( fontSize: 10.6.sp, color: ColorName.black50, )), ], ), ); }, itemCount: controller.funList.length)); } Widget buildUserEvaluateList() { return Obx(() { return Column( children: [ for (int index = 0; index < controller.evaluateList.length; index++) buildUserEvaluateItem(controller.evaluateList.value[index], index == controller.evaluateList.value.length - 1, index == 0) ], ); }); } Widget buildUserEvaluateItem( GoodsEvaluateInfo item, bool isLast, bool isFirst) { return Container( padding: EdgeInsets.symmetric(horizontal: 16.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: isFirst ? 16.w : 20.w), Row( children: [ CachedNetworkImage( imageUrl: item.avatar, width: 34.w, height: 34.w, ), SizedBox(width: 10.w), // 使用 Expanded 确保 Column 占据剩余宽度 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( item.name, style: TextStyle( fontSize: 14.sp, color: "#333333".color, fontWeight: FontWeight.w400), ), Spacer(), Assets.images.iconMemberCommentVerySatisfied .image(width: 16.w, height: 16.w), SizedBox(width: 5.w), Text( item.description, style: TextStyle( fontSize: 11.sp, color: "#DC8514".color, fontWeight: FontWeight.w500), ) ], ), SizedBox(height: 5.w), Text( item.content, style: TextStyle( fontSize: 11.sp, color: '#333333'.color, fontWeight: FontWeight.w400), maxLines: null, // 允许无限行数 softWrap: true, // 允许文本换行 ) ], ), ) ], ), SizedBox(height: 20.w), Visibility( visible: !isLast, child: Container( color: '#EEEEEE'.color, height: 1.w, ), ) ], ), ); } Widget buildPayWayView() { return Obx(() { return Visibility( visible: Platform.isIOS ? false : controller.payItemList.isNotEmpty, child: Container( margin: EdgeInsets.symmetric(horizontal: 16.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 12.w), Text("支付方式", style: TextStyle( fontSize: 16.sp, color: ColorName.black, fontWeight: FontWeight.bold)), SizedBox( height: 5.w, ), for (PayItemBean item in controller.payItemList) buildPayWayItem(item) ], ), ), ); }); } Widget buildPayWayItem(PayItemBean item) { return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => controller.onPayWayItemClick(item), child: Obx(() { bool isSelected = controller.selectedPayWay?.id == item.id; return Container( padding: EdgeInsets.symmetric(vertical: 12.w), child: Row( children: [ Image.asset( getPaymentIconPath( payMethod: item.payMethod, payPlatform: item.payPlatform), width: 20.w, height: 20.w), SizedBox(width: 8.w), Text(item.title, style: TextStyle( fontSize: 12.sp, color: ColorName.black, fontWeight: FontWeight.w400)), Spacer(), Image.asset( isSelected ? Assets.images.iconCbSelected.path : Assets.images.iconCbUnSelect.path, width: 16.w, height: 16.w), ], ), ); }), ); } }