member_page.dart 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993
  1. import 'dart:io';
  2. import 'package:cached_network_image/cached_network_image.dart';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:flutter/gestures.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter/src/widgets/framework.dart';
  7. import 'package:flutter_screenutil/flutter_screenutil.dart';
  8. import 'package:get/get.dart';
  9. import 'package:get/get_core/src/get_main.dart';
  10. import 'package:location/base/base_page.dart';
  11. import 'package:location/module/member/activity/member_activity_page.dart';
  12. import 'package:location/module/member/member_controller.dart';
  13. import 'package:location/resource/assets.gen.dart';
  14. import 'package:location/resource/colors.gen.dart';
  15. import 'package:location/utils/common_expand.dart';
  16. import 'package:location/utils/project_expand.dart';
  17. import 'package:location/widget/auto_scroll_list_view.dart';
  18. import '../../data/bean/goods_bean.dart';
  19. import '../../data/bean/goods_evaluate_info.dart';
  20. import '../../data/bean/member_status_info.dart';
  21. import '../../data/bean/pay_item_bean.dart';
  22. import '../../data/consts/payment_type.dart';
  23. import '../../resource/string.gen.dart';
  24. import '../../router/app_pages.dart';
  25. import '../../utils/date_util.dart';
  26. import '../../widget/activity_countdown_txt_view.dart';
  27. import '../../widget/animated_switcher_widget.dart';
  28. import '../../widget/shimmer_effect.dart';
  29. import 'member_discount_countdown_widget.dart';
  30. import 'member_header_cycle_widget.dart';
  31. ///进入会员类型
  32. enum MemberPageType {
  33. ///通用进入
  34. universalAccessEnter,
  35. ///倒计时试用完会员进入
  36. afeterTrialMemberEnter,
  37. ///加好友进入
  38. addFriendToEnter,
  39. //活动页
  40. activity,
  41. }
  42. class MemberPage extends BasePage<MemberController> {
  43. MemberPage({super.key, this.pageType});
  44. late MemberPageType? pageType = MemberPageType.universalAccessEnter;
  45. static Future<bool> start(
  46. {MemberPageType? enterTyp = MemberPageType.universalAccessEnter}) async {
  47. if (enterTyp == MemberPageType.activity) {
  48. return await MemberActivityPage.start() == true;
  49. } else {
  50. return await Get.toNamed(RoutePath.member, arguments: enterTyp) == true;
  51. }
  52. }
  53. @override
  54. bool immersive() {
  55. return true;
  56. }
  57. @override
  58. bool statusBarDarkFont() {
  59. return true;
  60. }
  61. @override
  62. Widget buildBody(BuildContext context) {
  63. return PopScope(
  64. canPop: !Platform.isAndroid,
  65. onPopInvokedWithResult: (bool didPop, dynamic result) async {
  66. if (didPop && Platform.isAndroid) {
  67. return;
  68. }
  69. controller.onPopBack(iosIsBack: false);
  70. },
  71. child: Stack(
  72. children: [buildScrollView(), buildHeadBar(), buildMemberBottomView()],
  73. ),
  74. );
  75. }
  76. Widget buildScrollView() {
  77. return SingleChildScrollView(
  78. physics: const ClampingScrollPhysics(),
  79. controller: controller.scrollController,
  80. child: Stack(
  81. children: [
  82. MemberHeaderCycleWidget(),
  83. Column(
  84. children: [
  85. SizedBox(
  86. height:
  87. 249.w + MediaQuery.of(Get.context!).padding.top - 24.w),
  88. Container(
  89. decoration: BoxDecoration(
  90. color: ColorName.white,
  91. borderRadius: BorderRadius.only(
  92. topLeft: Radius.circular(20.w),
  93. topRight: Radius.circular(20.w)),
  94. boxShadow: [
  95. BoxShadow(
  96. color: Color.fromRGBO(0, 63, 127, 0.10),
  97. blurRadius: 28.6,
  98. offset: Offset(0, -20),
  99. ),
  100. ],
  101. ),
  102. child: Stack(
  103. children: [
  104. Container(
  105. margin:
  106. EdgeInsets.only(left: 2.w, right: 2.w, top: 2.w),
  107. decoration: BoxDecoration(
  108. borderRadius: BorderRadius.only(
  109. topLeft: Radius.circular(20.w),
  110. topRight: Radius.circular(20.w)),
  111. image: DecorationImage(
  112. image: Assets.images.iconMemberVipMiddleBg
  113. .provider(),
  114. )),
  115. width: double.infinity,
  116. height: 174.w,
  117. ),
  118. Container(
  119. //width: double.infinity,
  120. decoration: BoxDecoration(
  121. borderRadius: BorderRadius.only(
  122. topLeft: Radius.circular(20.w),
  123. topRight: Radius.circular(20.w)),
  124. ),
  125. child: Column(
  126. crossAxisAlignment: CrossAxisAlignment.start,
  127. children: [
  128. SizedBox(height: 15.w),
  129. buildUserInfoView(),
  130. SizedBox(height: 12.w),
  131. buildGoodsList(),
  132. SizedBox(height: 12.w),
  133. buildPayWayView(),
  134. SizedBox(height: 12.w),
  135. Padding(
  136. padding: EdgeInsets.only(left: 16.w),
  137. child: Text(StringName.memberUserEvaluate,
  138. style: TextStyle(
  139. fontSize: 16.sp,
  140. color: ColorName.black,
  141. fontWeight: FontWeight.bold)),
  142. ),
  143. //SizedBox(height: 8.w),
  144. buildUserEvaluateList(),
  145. Obx(() {
  146. return Visibility(
  147. visible:
  148. controller.isShowMemberSubscribeTxt &&
  149. Platform.isIOS,
  150. child: Padding(
  151. padding: EdgeInsets.only(
  152. left: 16.w, right: 16.w, top: 16.w),
  153. child: Text(
  154. StringName.memberIosPayDesc,
  155. style: TextStyle(
  156. fontSize: 10.sp,
  157. color: ColorName.black60),
  158. ),
  159. ));
  160. }),
  161. SizedBox(
  162. height: 190.w +
  163. MediaQuery.of(Get.context!).padding.bottom)
  164. ],
  165. ),
  166. ),
  167. ],
  168. ),
  169. )
  170. ],
  171. ),
  172. ],
  173. ));
  174. }
  175. Widget buildGoodsList() {
  176. return Obx(() {
  177. final goods = controller.goodsList;
  178. final itemCount = goods.length;
  179. if (itemCount == 0) {
  180. return Container();
  181. }
  182. final widgets = <Widget>[];
  183. // 添加第一个商品(特殊样式)
  184. if (itemCount >= 1) {
  185. widgets.add(_createSpecialProduct(goods[0]));
  186. }
  187. // 如果有多个商品,添加普通样式商品
  188. if (itemCount >= 2) {
  189. widgets.add(SizedBox(height: 7.w));
  190. final rowItems = <Widget>[];
  191. // 计算可显示的普通商品数量(最多2个)
  192. final ordinaryCount = itemCount >= 3 ? 2 : 1;
  193. for (int i = 1; i <= ordinaryCount + 1 && i < itemCount; i++) {
  194. rowItems.add(Expanded(child: _ordinaryProductWidget(goods[i])));
  195. // 为除最后一个商品外的每个商品添加间距
  196. if (i < ordinaryCount + 1 && i < itemCount - 1) {
  197. rowItems.add(SizedBox(width: 8));
  198. }
  199. }
  200. widgets.add(Container(
  201. margin: EdgeInsets.only(left: 16.w, right: 16.w),
  202. child: Row(
  203. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  204. children: rowItems,
  205. ),
  206. ));
  207. }
  208. return Column(
  209. children: widgets,
  210. );
  211. });
  212. }
  213. //特惠产品
  214. Widget _createSpecialProduct(GoodsBean goodsInfo) {
  215. bool isSelected = controller.selectedGoods?.id == goodsInfo.id;
  216. return GestureDetector(
  217. onTap: () {
  218. controller.onGoodsItemClick(goodsInfo);
  219. },
  220. child: Container(
  221. height: 96.w,
  222. margin: EdgeInsets.only(left: 12.w, right: 16.w),
  223. padding: EdgeInsets.only(left: 17.w),
  224. decoration: BoxDecoration(
  225. image: DecorationImage(
  226. image: isSelected
  227. ? Assets.images.iconMemberSpecialProductsSelect.provider()
  228. : Assets.images.iconMemberSpecialProductsNormal.provider(),
  229. fit: BoxFit.fill,
  230. ),
  231. ),
  232. child: Column(
  233. children: [
  234. SizedBox(
  235. height: 29.w,
  236. ),
  237. Row(
  238. children: [
  239. Column(
  240. children: [
  241. RichText(
  242. text: TextSpan(
  243. style: TextStyle(
  244. color: isSelected
  245. ? '#FF5656'.color
  246. : "#323133".color,
  247. fontWeight: FontWeight.bold),
  248. children: [
  249. TextSpan(
  250. text: '¥',
  251. style: TextStyle(fontSize: 16.sp, height: 1)),
  252. TextSpan(
  253. text: goodsInfo.amount.divideBy100(),
  254. style: TextStyle(
  255. fontSize: 28.sp,
  256. height: 1,
  257. //fontFamily: FontFamily.oppoSans
  258. ))
  259. ])),
  260. Text('¥${goodsInfo.originalAmount.divideBy100()}',
  261. style: TextStyle(
  262. decoration: TextDecoration.lineThrough,
  263. decorationColor: ColorName.black40,
  264. decorationThickness: 1.0,
  265. fontSize: 12.sp,
  266. color: ColorName.black40))
  267. ],
  268. ),
  269. SizedBox(
  270. width: 22.24.w,
  271. ),
  272. Column(
  273. crossAxisAlignment: CrossAxisAlignment.start,
  274. children: [
  275. Text(
  276. goodsInfo.name,
  277. style: TextStyle(
  278. fontSize: 17.sp,
  279. color: "#333333".color,
  280. fontWeight: FontWeight.bold),
  281. ),
  282. SizedBox(
  283. height: 3.w,
  284. ),
  285. Text(
  286. goodsInfo.description ?? "",
  287. style: TextStyle(
  288. fontSize: 11.sp,
  289. color: "#9191BA".color,
  290. fontWeight: FontWeight.bold),
  291. ),
  292. ],
  293. ),
  294. ],
  295. ),
  296. ],
  297. ),
  298. ),
  299. );
  300. }
  301. ///普通产品
  302. Widget _ordinaryProductWidget(GoodsBean goodsInfo) {
  303. bool isSelected = controller.selectedGoods?.id == goodsInfo.id;
  304. return GestureDetector(
  305. onTap: () {
  306. controller.onGoodsItemClick(goodsInfo);
  307. },
  308. child: SizedBox(
  309. width: double.infinity,
  310. height: 62.w,
  311. child: Stack(
  312. children: [
  313. Container(
  314. width: double.infinity,
  315. height: double.infinity,
  316. decoration: BoxDecoration(
  317. gradient: LinearGradient(
  318. colors: ['#FFFFFF'.color, '#F3EAFF'.color],
  319. begin: Alignment.topLeft,
  320. end: Alignment.bottomRight,
  321. stops: const [0.4, 1.0]),
  322. borderRadius: BorderRadius.circular(12.w),
  323. border: Border.all(
  324. color: isSelected ? '#7A13C6'.color : '#F1E6FF'.color,
  325. width: isSelected ? 2.5.w : 1.w,
  326. ))),
  327. Container(
  328. padding: EdgeInsets.only(left: 16.w, right: 12.w),
  329. child: Column(
  330. children: [
  331. SizedBox(
  332. height: 15.w,
  333. ),
  334. Row(
  335. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  336. children: [
  337. Column(
  338. crossAxisAlignment: CrossAxisAlignment.start,
  339. children: [
  340. Text(
  341. goodsInfo.name,
  342. style: TextStyle(
  343. fontSize: 12.sp,
  344. color: "#333333".color,
  345. fontWeight: FontWeight.bold),
  346. ),
  347. //SizedBox(height: 6.w,),
  348. Text(
  349. goodsInfo.description ?? "",
  350. style: TextStyle(
  351. fontSize: 10.sp,
  352. color: ColorName.black40,
  353. fontWeight: FontWeight.bold),
  354. ),
  355. ],
  356. ),
  357. //SizedBox(width: 22.24.w,),
  358. Column(
  359. crossAxisAlignment: CrossAxisAlignment.end,
  360. children: [
  361. RichText(
  362. text: TextSpan(
  363. style: TextStyle(
  364. color: isSelected
  365. ? '#FF5656'.color
  366. : "#323133".color,
  367. fontWeight: FontWeight.bold),
  368. children: [
  369. TextSpan(
  370. text: '¥',
  371. style: TextStyle(
  372. color: isSelected
  373. ? "#FF5656".color
  374. : ColorName.black80,
  375. fontSize: 11.sp,
  376. height: 1)),
  377. TextSpan(
  378. text: goodsInfo.amount.divideBy100(),
  379. style: TextStyle(
  380. fontSize: 20.sp,
  381. height: 1,
  382. ))
  383. ])),
  384. Text('¥${goodsInfo.originalAmount.divideBy100()}',
  385. style: TextStyle(
  386. decoration: TextDecoration.lineThrough,
  387. decorationColor: ColorName.black30,
  388. decorationThickness: 1.0,
  389. fontSize: 10.sp,
  390. color: ColorName.black30))
  391. ],
  392. ),
  393. ],
  394. ),
  395. ],
  396. ),
  397. )
  398. ],
  399. ),
  400. ),
  401. );
  402. }
  403. Widget buildMemberBottomView() {
  404. return Obx(() {
  405. if (controller.memberStatusInfo != null &&
  406. controller.memberStatusInfo?.permanent == true) {
  407. return SizedBox(); // 不显示
  408. }
  409. return Align(
  410. alignment: Alignment.bottomCenter,
  411. child: Stack(
  412. children: [
  413. IgnorePointer(
  414. ignoring: true, // 不处理事件
  415. child: Container(
  416. height: 190.w + MediaQuery.of(Get.context!).padding.bottom,
  417. decoration: BoxDecoration(
  418. gradient: LinearGradient(
  419. begin: Alignment.bottomCenter,
  420. end: Alignment.topCenter,
  421. colors: [
  422. Colors.white,
  423. "#00FFFFFF".color,
  424. ],
  425. stops: [0.4368, 1.0],
  426. ),
  427. ),
  428. ),
  429. ),
  430. Positioned(
  431. left: 0,
  432. right: 0,
  433. bottom: MediaQuery.of(Get.context!).padding.bottom,
  434. child: IntrinsicHeight(
  435. child: Column(
  436. children: [
  437. Stack(
  438. children: [
  439. Container(
  440. width: 336.w,
  441. height: 47.w,
  442. decoration: BoxDecoration(
  443. borderRadius: BorderRadius.only(
  444. topLeft: Radius.circular(30.w),
  445. topRight: Radius.circular(30.w),
  446. ),
  447. color: '#FFFED8'.color),
  448. child: Align(
  449. alignment: Alignment(0.0, -0.75),
  450. child: Row(
  451. mainAxisAlignment: MainAxisAlignment.center,
  452. children: [
  453. Text(
  454. StringName.memberActivityCountdown,
  455. style: TextStyle(
  456. fontSize: 11.sp,
  457. color: '#FF5656'.color),
  458. ),
  459. SizedBox(width: 4.w),
  460. Obx(() {
  461. return ActivityCountdownTextView(
  462. timeItemHeight: 15.w,
  463. contentPadding: EdgeInsets.zero,
  464. timeItemWidth: 16.w,
  465. textStyle: TextStyle(
  466. fontSize: 10.sp,
  467. color: Colors.white),
  468. duration: controller.activityDuration ??
  469. Duration(seconds: 0),
  470. separator: buildCountdownSeparator(),
  471. timeBgBoxDecoration: BoxDecoration(
  472. color: '#FF5656'.color,
  473. borderRadius:
  474. BorderRadius.circular(3.w),
  475. ));
  476. }),
  477. SizedBox(width: 4.w),
  478. Text(
  479. StringName
  480. .memberActivitySpeciallyPreferential,
  481. style: TextStyle(
  482. fontSize: 10.sp,
  483. color: '#FF5656'.color),
  484. )
  485. ],
  486. ),
  487. ),
  488. ),
  489. GestureDetector(
  490. onTap: controller.onBuyClick,
  491. child: Container(
  492. margin: EdgeInsets.only(top: 24.w),
  493. height: 50.w,
  494. width: 336.w,
  495. child: ShimmerEffect(
  496. image:
  497. Assets.images.imgMemberBtnShadow.provider(),
  498. shimmerWidthFactor: 0.244047619047619,
  499. duration: Duration(milliseconds: 3000),
  500. delay: Duration(milliseconds: 800),
  501. child: Container(
  502. height: 50.w,
  503. width: 336.w,
  504. alignment: Alignment.center,
  505. decoration: BoxDecoration(
  506. image: DecorationImage(
  507. image: Assets
  508. .images.iconMemberSettlementBg
  509. .provider(),
  510. fit: BoxFit.fill)),
  511. child: Text(
  512. StringName.memberVipUnlock,
  513. style: TextStyle(
  514. fontSize: 18.sp,
  515. color: '#FFF8EF'.color,
  516. fontWeight: FontWeight.bold),
  517. ),
  518. ),
  519. ),
  520. ),
  521. )
  522. ],
  523. ),
  524. SizedBox(height: 8.w),
  525. buildPrivacyPolicyView(),
  526. SizedBox(height: 12.w),
  527. ],
  528. ),
  529. ),
  530. )
  531. ],
  532. ));
  533. });
  534. }
  535. Widget buildCountdownSeparator() {
  536. return Container(
  537. margin: EdgeInsets.symmetric(horizontal: 2.w),
  538. child: IntrinsicHeight(
  539. child: Column(
  540. children: [
  541. Container(
  542. width: 2.w,
  543. height: 2.w,
  544. decoration: BoxDecoration(
  545. color: '#FF5656'.color,
  546. shape: BoxShape.circle,
  547. ),
  548. ),
  549. SizedBox(height: 3.w),
  550. Container(
  551. width: 2.w,
  552. height: 2.w,
  553. decoration: BoxDecoration(
  554. color: '#FF5656'.color,
  555. shape: BoxShape.circle,
  556. ),
  557. )
  558. ],
  559. ),
  560. ),
  561. );
  562. }
  563. Widget buildUserInfoView() {
  564. return Row(
  565. children: [
  566. SizedBox(width: 18.w),
  567. controller.isLogin
  568. ? ClipOval(
  569. child: Container(
  570. width: 32.w,
  571. height: 32.w,
  572. child: CachedNetworkImage(
  573. imageUrl: controller.memberStatusInfo?.avatar ?? "",
  574. fit: BoxFit.cover,
  575. ),
  576. ),
  577. )
  578. : Assets.images.iconMemberAvatar.image(width: 32.w, height: 32.w),
  579. SizedBox(width: 7.w),
  580. Column(
  581. crossAxisAlignment: CrossAxisAlignment.start,
  582. children: [
  583. Obx(() {
  584. return GestureDetector(
  585. onTap: controller.onLoginClick,
  586. child: Text(
  587. controller.phone?.isNotEmpty == true
  588. ? controller.getUserName(controller.phone!)
  589. : StringName.mineAccountGoLogin,
  590. style: TextStyle(
  591. fontSize: 13.sp,
  592. color: "#333333".color,
  593. fontWeight: FontWeight.bold)),
  594. );
  595. }),
  596. Container(
  597. height: 16.w,
  598. width: MediaQuery.of(Get.context!).size.width - 77.w,
  599. child: Row(
  600. // 主轴默认左对齐,通过Spacer推挤右侧内容
  601. children: [
  602. // 左侧内容:会员等级描述 + VIP卡片描述
  603. Visibility(
  604. visible: MemberStatusInfo.getLevelDesc(
  605. controller.memberStatusInfo)
  606. .isNotEmpty,
  607. child: Text(
  608. MemberStatusInfo.getLevelDesc(
  609. controller.memberStatusInfo),
  610. style: TextStyle(
  611. fontSize: 11.sp,
  612. fontWeight: FontWeight.w700,
  613. color: '#9144F8'.color)),
  614. ),
  615. buildMemberCardVipDesc(),
  616. Spacer(),
  617. ],
  618. ),
  619. )
  620. ],
  621. ),
  622. SizedBox(width: 20.w)
  623. ],
  624. );
  625. }
  626. Widget buildMemberCardVipDesc() {
  627. return Obx(() {
  628. String desc = '';
  629. if (!controller.isLogin) {
  630. if ((controller.memberStatusInfo?.endTimestamp ?? 0) > 0) {
  631. desc =
  632. '${DateUtil.fromMillisecondsSinceEpoch('yyyy-MM-dd', controller.memberStatusInfo?.endTimestamp ?? 0)} ${StringName.memberCardExpirationDesc}';
  633. } else {
  634. desc = StringName.memberCardNoLoginDesc;
  635. }
  636. } else if (controller.memberStatusInfo == null ||
  637. controller.memberStatusInfo?.expired == true) {
  638. desc = StringName.memberCardNoVipDesc;
  639. } else if (controller.memberStatusInfo?.expired == false &&
  640. controller.memberStatusInfo?.permanent == true) {
  641. desc = StringName.memberCardPermanentVipDesc;
  642. } else {
  643. desc =
  644. '${DateUtil.fromMillisecondsSinceEpoch('yyyy-MM-dd', controller.memberStatusInfo?.endTimestamp ?? 0)} ${StringName.memberCardExpirationDesc}';
  645. }
  646. return Text(desc,
  647. style: TextStyle(
  648. fontSize: 11.sp,
  649. color: ColorName.black50,
  650. fontWeight: FontWeight.w400));
  651. });
  652. }
  653. Widget buildHeadBar() {
  654. return Column(
  655. children: [
  656. SizedBox(
  657. height: MediaQuery.of(Get.context!).padding.top,
  658. ),
  659. SizedBox(
  660. width: double.infinity,
  661. height: 56.w,
  662. child: Row(
  663. crossAxisAlignment: CrossAxisAlignment.center,
  664. children: [
  665. SizedBox(
  666. width: 15.w,
  667. ),
  668. GestureDetector(
  669. onTap: () => controller.onPopBack(),
  670. child: Assets.images.iconMemberVipBack
  671. .image(width: 26.w, height: 26..w),
  672. ),
  673. SizedBox(
  674. width: 10.w,
  675. ),
  676. Container(
  677. width: MediaQuery.of(Get.context!).size.width - 51.w,
  678. padding: EdgeInsets.only(right: 12.w),
  679. child: buildVerticalSlideshowWidget()),
  680. ],
  681. ),
  682. )
  683. ],
  684. );
  685. }
  686. Widget buildVerticalSlideshowWidget() {
  687. return Row(
  688. children: [
  689. Expanded(
  690. child: Center(
  691. child: Container(
  692. width: 192.w,
  693. height: 26.w,
  694. decoration: BoxDecoration(
  695. color: ColorName.black40,
  696. borderRadius: BorderRadius.circular(87.w),
  697. ),
  698. child: Center(
  699. child: AnimatedSwitcherWidget(
  700. controller: controller.switcherController)),
  701. ),
  702. ),
  703. ),
  704. if (Platform.isAndroid)
  705. SizedBox(
  706. width: 40.w,
  707. ),
  708. if (Platform.isIOS)
  709. Obx(() {
  710. return Visibility(
  711. visible: Platform.isIOS &&
  712. controller.accountRepository.isLogin.value,
  713. child: Row(
  714. children: [
  715. SizedBox(
  716. width: 26.w,
  717. ),
  718. GestureDetector(
  719. onTap: controller.clickRecoverSubscribe,
  720. child: Container(
  721. height: 26.w,
  722. decoration: BoxDecoration(
  723. color: ColorName.black40,
  724. borderRadius: BorderRadius.circular(26.w / 2.0),
  725. ),
  726. padding: EdgeInsets.symmetric(horizontal: 10.w),
  727. child: Row(
  728. children: [
  729. Assets.images.iconAppleRecoverSubscribe
  730. .image(width: 14.w, height: 14.w),
  731. Text(StringName.appleRecoverSubscribeTxt,
  732. style: TextStyle(
  733. fontSize: 11.sp,
  734. color: ColorName.white,
  735. fontWeight: FontWeight.w500)),
  736. ],
  737. ),
  738. ),
  739. )
  740. ],
  741. ));
  742. })
  743. ],
  744. );
  745. }
  746. Widget buildPrivacyPolicyView() {
  747. return Padding(
  748. padding: EdgeInsets.only(left: 12.w),
  749. child: RichText(
  750. text: TextSpan(
  751. style: TextStyle(fontSize: 12.sp, color: ColorName.black40),
  752. children: [
  753. TextSpan(text: '购买前请先阅读'),
  754. TextSpan(
  755. recognizer: TapGestureRecognizer()
  756. ..onTap = () {
  757. controller.onPrivacyPolicyClick();
  758. },
  759. text: '隐私政策',
  760. style: TextStyle(
  761. color: ColorName.black60,
  762. decoration: TextDecoration.underline)),
  763. TextSpan(text: '&'),
  764. TextSpan(
  765. recognizer: TapGestureRecognizer()
  766. ..onTap = () {
  767. controller.onTermOfServiceClick();
  768. },
  769. text: '服务条款',
  770. style: TextStyle(
  771. color: ColorName.black60,
  772. decoration: TextDecoration.underline)),
  773. if (Platform.isIOS) TextSpan(text: '&'),
  774. if (Platform.isIOS)
  775. TextSpan(
  776. recognizer: TapGestureRecognizer()
  777. ..onTap = () {
  778. controller.onRenewalAgreementClick();
  779. },
  780. text: '会员协议',
  781. style: TextStyle(
  782. color: ColorName.black60,
  783. decoration: TextDecoration.underline)),
  784. ])),
  785. );
  786. }
  787. Widget buildFunctionList() {
  788. return SizedBox(
  789. height: 80.w,
  790. child: AutoScrollListView(
  791. padding: EdgeInsets.only(left: 12.w),
  792. itemBuilder: (ctx, index) {
  793. final item = controller.funList[index];
  794. return Padding(
  795. padding: EdgeInsets.only(right: 20.w),
  796. child: Column(
  797. children: [
  798. Image.asset(item.iconPath, width: 36.w, height: 36.w),
  799. Spacer(flex: 3),
  800. Text(item.funName,
  801. style: TextStyle(
  802. fontSize: 12.8.sp,
  803. color: ColorName.black90,
  804. fontWeight: FontWeight.bold)),
  805. Spacer(flex: 2),
  806. Text(item.funDesc,
  807. style: TextStyle(
  808. fontSize: 10.6.sp,
  809. color: ColorName.black50,
  810. )),
  811. ],
  812. ),
  813. );
  814. },
  815. itemCount: controller.funList.length));
  816. }
  817. Widget buildUserEvaluateList() {
  818. return Obx(() {
  819. return Column(
  820. children: [
  821. for (int index = 0; index < controller.evaluateList.length; index++)
  822. buildUserEvaluateItem(controller.evaluateList.value[index],
  823. index == controller.evaluateList.value.length - 1, index == 0)
  824. ],
  825. );
  826. });
  827. }
  828. Widget buildUserEvaluateItem(
  829. GoodsEvaluateInfo item, bool isLast, bool isFirst) {
  830. return Container(
  831. padding: EdgeInsets.symmetric(horizontal: 16.w),
  832. child: Column(
  833. crossAxisAlignment: CrossAxisAlignment.start,
  834. children: [
  835. SizedBox(height: isFirst ? 16.w : 20.w),
  836. Row(
  837. children: [
  838. CachedNetworkImage(
  839. imageUrl: item.avatar,
  840. width: 34.w,
  841. height: 34.w,
  842. ),
  843. SizedBox(width: 10.w),
  844. // 使用 Expanded 确保 Column 占据剩余宽度
  845. Expanded(
  846. child: Column(
  847. crossAxisAlignment: CrossAxisAlignment.start,
  848. children: [
  849. Row(
  850. children: [
  851. Text(
  852. item.name,
  853. style: TextStyle(
  854. fontSize: 14.sp,
  855. color: "#333333".color,
  856. fontWeight: FontWeight.w400),
  857. ),
  858. Spacer(),
  859. Assets.images.iconMemberCommentVerySatisfied
  860. .image(width: 16.w, height: 16.w),
  861. SizedBox(width: 5.w),
  862. Text(
  863. item.description,
  864. style: TextStyle(
  865. fontSize: 11.sp,
  866. color: "#DC8514".color,
  867. fontWeight: FontWeight.w500),
  868. )
  869. ],
  870. ),
  871. SizedBox(height: 5.w),
  872. Text(
  873. item.content,
  874. style: TextStyle(
  875. fontSize: 11.sp,
  876. color: '#333333'.color,
  877. fontWeight: FontWeight.w400),
  878. maxLines: null, // 允许无限行数
  879. softWrap: true, // 允许文本换行
  880. )
  881. ],
  882. ),
  883. )
  884. ],
  885. ),
  886. SizedBox(height: 20.w),
  887. Visibility(
  888. visible: !isLast,
  889. child: Container(
  890. color: '#EEEEEE'.color,
  891. height: 1.w,
  892. ),
  893. )
  894. ],
  895. ),
  896. );
  897. }
  898. Widget buildPayWayView() {
  899. return Obx(() {
  900. return Visibility(
  901. visible: Platform.isIOS ? false : controller.payItemList.isNotEmpty,
  902. child: Container(
  903. margin: EdgeInsets.symmetric(horizontal: 16.w),
  904. child: Column(
  905. crossAxisAlignment: CrossAxisAlignment.start,
  906. children: [
  907. SizedBox(height: 12.w),
  908. Text("支付方式",
  909. style: TextStyle(
  910. fontSize: 16.sp,
  911. color: ColorName.black,
  912. fontWeight: FontWeight.bold)),
  913. SizedBox(
  914. height: 5.w,
  915. ),
  916. for (PayItemBean item in controller.payItemList)
  917. buildPayWayItem(item)
  918. ],
  919. ),
  920. ),
  921. );
  922. });
  923. }
  924. Widget buildPayWayItem(PayItemBean item) {
  925. return GestureDetector(
  926. behavior: HitTestBehavior.translucent,
  927. onTap: () => controller.onPayWayItemClick(item),
  928. child: Obx(() {
  929. bool isSelected = controller.selectedPayWay?.id == item.id;
  930. return Container(
  931. padding: EdgeInsets.symmetric(vertical: 12.w),
  932. child: Row(
  933. children: [
  934. Image.asset(
  935. getPaymentIconPath(
  936. payMethod: item.payMethod, payPlatform: item.payPlatform),
  937. width: 20.w,
  938. height: 20.w),
  939. SizedBox(width: 8.w),
  940. Text(item.title,
  941. style: TextStyle(
  942. fontSize: 12.sp,
  943. color: ColorName.black,
  944. fontWeight: FontWeight.w400)),
  945. Spacer(),
  946. Image.asset(
  947. isSelected
  948. ? Assets.images.iconCbSelected.path
  949. : Assets.images.iconCbUnSelect.path,
  950. width: 16.w,
  951. height: 16.w),
  952. ],
  953. ),
  954. );
  955. }),
  956. );
  957. }
  958. }