member_page.dart 35 KB

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