member_page.dart 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. import 'dart:io';
  2. import 'package:flutter/cupertino.dart';
  3. import 'package:flutter/gestures.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter/src/widgets/framework.dart';
  6. import 'package:flutter_screenutil/flutter_screenutil.dart';
  7. import 'package:get/get.dart';
  8. import 'package:get/get_core/src/get_main.dart';
  9. import 'package:location/base/base_page.dart';
  10. import 'package:location/module/member/member_controller.dart';
  11. import 'package:location/resource/assets.gen.dart';
  12. import 'package:location/resource/colors.gen.dart';
  13. import 'package:location/utils/common_expand.dart';
  14. import 'package:location/utils/project_expand.dart';
  15. import 'package:location/widget/auto_scroll_list_view.dart';
  16. import '../../data/bean/member_status_info.dart';
  17. import '../../data/bean/pay_item_bean.dart';
  18. import '../../data/consts/payment_type.dart';
  19. import '../../resource/fonts.gen.dart';
  20. import '../../resource/string.gen.dart';
  21. import '../../router/app_pages.dart';
  22. import '../../utils/date_util.dart';
  23. import '../../widget/animated_switcher_widget.dart';
  24. import 'member_evaluate_bean.dart';
  25. ///进入会员类型
  26. enum MemberPageType {
  27. ///通用进入
  28. universalAccessEnter,
  29. ///倒计时试用完会员进入
  30. afeterTrialMemberEnter,
  31. ///加好友进入
  32. addFriendToEnter,
  33. }
  34. class MemberPage extends BasePage<MemberController> {
  35. MemberPage({super.key,this.pageType});
  36. late MemberPageType? pageType = MemberPageType.universalAccessEnter;
  37. static void start({MemberPageType? enterTyp = MemberPageType.universalAccessEnter}) {
  38. Get.toNamed(RoutePath.member,arguments: enterTyp);
  39. }
  40. @override
  41. bool immersive() {
  42. return true;
  43. }
  44. @override
  45. bool statusBarDarkFont() {
  46. return false;
  47. }
  48. @override
  49. Widget buildBody(BuildContext context) {
  50. return PopScope(
  51. canPop: false,
  52. onPopInvokedWithResult: (bool didPop, dynamic result) async {
  53. controller.onPopBack();
  54. },
  55. child: Stack(
  56. children: [
  57. SingleChildScrollView(
  58. physics: const ClampingScrollPhysics(),
  59. controller: controller.scrollController,
  60. child: Stack(
  61. children: [
  62. Assets.images.bgMemberHeader.image(width: double.infinity),
  63. SafeArea(
  64. child: Column(
  65. children: [
  66. SizedBox(height: 62.w),
  67. buildUserInfoView(),
  68. SizedBox(height: 26.w),
  69. Container(
  70. width: double.infinity,
  71. decoration: BoxDecoration(
  72. borderRadius: BorderRadius.only(
  73. topLeft: Radius.circular(14.w),
  74. topRight: Radius.circular(14.w)),
  75. gradient: LinearGradient(
  76. begin: Alignment.topCenter,
  77. end: Alignment.bottomCenter,
  78. stops: [0.0, 0.1],
  79. colors: ['#EFE9FF'.color, Colors.white])),
  80. child: Column(
  81. crossAxisAlignment: CrossAxisAlignment.start,
  82. children: [
  83. SizedBox(height: 23.w),
  84. buildGoodsList(),
  85. SizedBox(height: 12.w),
  86. buildPrivacyPolicyView(),
  87. buildPayWayView(),
  88. SizedBox(height: 30.w),
  89. Padding(
  90. padding: EdgeInsets.only(left: 12.w),
  91. child: Text(StringName.memberEquityIntroduction,
  92. style: TextStyle(
  93. fontSize: 16.sp,
  94. color: ColorName.black90,
  95. fontWeight: FontWeight.bold)),
  96. ),
  97. SizedBox(height: 19.w),
  98. buildFunctionList(),
  99. SizedBox(height: 40.w),
  100. Padding(
  101. padding: EdgeInsets.only(left: 12.w),
  102. child: Text(StringName.memberUserEvaluate,
  103. style: TextStyle(
  104. fontSize: 16.sp,
  105. color: ColorName.black90,
  106. fontWeight: FontWeight.bold)),
  107. ),
  108. SizedBox(height: 8.w),
  109. buildUserEvaluateList(),
  110. SizedBox(height: 20.w),
  111. Container(
  112. padding: EdgeInsets.all(12.w),
  113. decoration: BoxDecoration(
  114. color: '#F7F7F7'.color,
  115. borderRadius: BorderRadius.circular(6.w)),
  116. margin: EdgeInsets.symmetric(horizontal: 12.w),
  117. child: Text(StringName.memberTips,
  118. style: TextStyle(
  119. fontSize: 12.sp, color: '#A7A7A7'.color)),
  120. ),
  121. SizedBox(height: 100.w)
  122. ],
  123. ),
  124. )
  125. ],
  126. ),
  127. )
  128. ],
  129. ),
  130. ),
  131. buildHeadBar(),
  132. buildMemberBottomView()
  133. ],
  134. ),
  135. );
  136. }
  137. Widget buildGoodsList() {
  138. return Obx(() {
  139. return SizedBox(
  140. height: 123.w,
  141. child: ListView.builder(
  142. padding: EdgeInsets.only(left: 12.w),
  143. physics: const BouncingScrollPhysics(
  144. parent: AlwaysScrollableScrollPhysics()),
  145. scrollDirection: Axis.horizontal,
  146. itemBuilder: (BuildContext ctx, int index) {
  147. return Obx(() {
  148. final item = controller.goodsList[index];
  149. bool isSelected = controller.selectedGoods?.id == item.id;
  150. return GestureDetector(
  151. behavior: HitTestBehavior.translucent,
  152. onTap: () => controller.onGoodsItemClick(item),
  153. child: Container(
  154. margin: EdgeInsets.only(right: 10.w),
  155. width: 138.w,
  156. height: 123.w,
  157. child: Stack(
  158. children: [
  159. Container(
  160. width: double.infinity,
  161. height: double.infinity,
  162. decoration: BoxDecoration(
  163. color: isSelected
  164. ? '#6BC4BAFF'.color
  165. : ColorName.white20,
  166. borderRadius: BorderRadius.circular(18.w),
  167. border: Border.all(
  168. color: isSelected
  169. ? '#C4BAFF'.color
  170. : '#E8E8E8'.color,
  171. width: 3.w)),
  172. padding: EdgeInsets.only(left: 10.w),
  173. child: Column(
  174. crossAxisAlignment: CrossAxisAlignment.start,
  175. children: [
  176. Spacer(flex: 2),
  177. Text(
  178. item.name,
  179. style: TextStyle(
  180. fontSize: 14.sp,
  181. color: ColorName.black90,
  182. fontWeight: FontWeight.bold),
  183. ),
  184. SizedBox(height: 3.w),
  185. Text('¥${item.originalAmount.divideBy100()}',
  186. style: TextStyle(
  187. decoration: TextDecoration.lineThrough,
  188. fontSize: 10.sp,
  189. color: ColorName.black60)),
  190. Spacer(flex: 1),
  191. RichText(
  192. text: TextSpan(
  193. style: TextStyle(
  194. color: isSelected
  195. ? '#EA1231'.color
  196. : ColorName.black80,
  197. fontWeight: FontWeight.bold),
  198. children: [
  199. TextSpan(
  200. text: '¥',
  201. style: TextStyle(
  202. fontSize: 20.sp, height: 1)),
  203. TextSpan(
  204. text: item.amount.divideBy100(),
  205. style: TextStyle(
  206. fontSize: 34.sp,
  207. height: 1,
  208. fontFamily: FontFamily.oppoSans))
  209. ])),
  210. Padding(
  211. padding: EdgeInsets.only(left: 7.w),
  212. child: Text(item.description ?? '',
  213. style: TextStyle(
  214. fontSize: 12.sp,
  215. color: ColorName.black40)),
  216. ),
  217. Spacer(
  218. flex: 1,
  219. )
  220. ],
  221. ),
  222. ),
  223. Visibility(
  224. visible: item.tag?.isNotEmpty == true,
  225. child: Positioned(
  226. top: 0,
  227. right: 0,
  228. child: Container(
  229. decoration: BoxDecoration(
  230. gradient: LinearGradient(colors: [
  231. '#A26CFF'.color,
  232. '#FF7CD8'.color,
  233. '#898BFF'.color
  234. ]),
  235. borderRadius: BorderRadius.only(
  236. topRight: Radius.circular(11.w),
  237. bottomLeft: Radius.circular(11.w))),
  238. padding: EdgeInsets.symmetric(
  239. horizontal: 10.w, vertical: 4.w),
  240. child: Text(item.tag ?? '',
  241. style: TextStyle(
  242. fontSize: 12.sp,
  243. color: Colors.white)),
  244. ),
  245. ))
  246. ],
  247. )),
  248. );
  249. });
  250. },
  251. itemCount: controller.goodsList.length),
  252. );
  253. });
  254. }
  255. Widget buildMemberBottomView() {
  256. return Obx(() {
  257. return Visibility(
  258. visible: controller.memberStatusInfo == null ||
  259. controller.memberStatusInfo?.permanent == false,
  260. child: Align(
  261. alignment: Alignment.bottomCenter,
  262. child: Container(
  263. padding: EdgeInsets.only(
  264. left: 12.w, right: 12.w, top: 13.w, bottom: 20.w),
  265. decoration: BoxDecoration(
  266. color: Colors.white,
  267. boxShadow: [
  268. BoxShadow(
  269. color: Colors.black.withOpacity(0.05),
  270. offset: Offset(0, -2),
  271. blurRadius: 4)
  272. ],
  273. borderRadius: BorderRadius.only(
  274. topLeft: Radius.circular(16.w),
  275. topRight: Radius.circular(16.w)),
  276. ),
  277. child: Obx(() {
  278. return Row(
  279. children: [
  280. Text('¥',
  281. style: TextStyle(
  282. fontSize: 14.sp,
  283. color: '#EA1231'.color,
  284. fontWeight: FontWeight.bold)),
  285. Text(
  286. controller.selectedGoods?.amount.divideBy100() ?? '--',
  287. style: TextStyle(
  288. fontSize: 24.sp,
  289. color: '#EA1231'.color,
  290. fontWeight: FontWeight.bold),
  291. ),
  292. Text(
  293. ' / ${controller.selectedGoods?.name ?? '--'}',
  294. style: TextStyle(fontSize: 12.sp, color: '#000000'.color),
  295. ),
  296. Spacer(),
  297. GestureDetector(
  298. onTap: controller.onBuyClick,
  299. child: Container(
  300. decoration: BoxDecoration(
  301. color: ColorName.colorPrimary,
  302. borderRadius: BorderRadius.circular(100.w)),
  303. width: 164.w,
  304. height: 44.w,
  305. child: Center(
  306. child: Text(
  307. controller.memberStatusInfo?.expired == false
  308. ? StringName.memberVipRenew
  309. : StringName.memberVipUnlock,
  310. style: TextStyle(
  311. fontSize: 15.sp,
  312. color: Colors.white,
  313. fontWeight: FontWeight.bold),
  314. ),
  315. ),
  316. ),
  317. )
  318. ],
  319. );
  320. }),
  321. ),
  322. ),
  323. );
  324. });
  325. }
  326. Widget buildUserInfoView() {
  327. return Row(
  328. children: [
  329. SizedBox(width: 20.w),
  330. Assets.images.iconMemberAvatar.image(width: 40.w, height: 40.w),
  331. SizedBox(width: 10.w),
  332. Column(
  333. crossAxisAlignment: CrossAxisAlignment.start,
  334. children: [
  335. Obx(() {
  336. return GestureDetector(
  337. onTap: controller.onLoginClick,
  338. child: Text(
  339. controller.phone?.isNotEmpty == true
  340. ? controller.getUserName(controller.phone!)
  341. : StringName.mineAccountGoLogin,
  342. style: TextStyle(
  343. fontSize: 16.sp,
  344. color: Colors.white,
  345. fontWeight: FontWeight.bold)),
  346. );
  347. }),
  348. buildMemberCardVipDesc()
  349. ],
  350. ),
  351. Spacer(),
  352. Container(
  353. decoration: BoxDecoration(
  354. color: '#272F51'.color,
  355. border: Border.all(color: '#99CAB0F0'.color, width: 1.w),
  356. borderRadius: BorderRadius.circular(100.w),
  357. ),
  358. padding: EdgeInsets.symmetric(horizontal: 18.w, vertical: 6.w),
  359. child: Obx(() {
  360. return Text(
  361. MemberStatusInfo.getLevelDesc(controller.memberStatusInfo),
  362. style: TextStyle(fontSize: 12.sp, color: '#D2CCFF'.color));
  363. })),
  364. SizedBox(width: 18.w)
  365. ],
  366. );
  367. }
  368. Widget buildMemberCardVipDesc() {
  369. return Obx(() {
  370. String desc = '';
  371. if (!controller.isLogin) {
  372. desc = StringName.memberCardNoLoginDesc;
  373. } else if (controller.memberStatusInfo == null ||
  374. controller.memberStatusInfo?.expired == true) {
  375. desc = StringName.memberCardNoVipDesc;
  376. } else if (controller.memberStatusInfo?.expired == false &&
  377. controller.memberStatusInfo?.permanent == true) {
  378. desc = StringName.memberCardPermanentVipDesc;
  379. } else {
  380. desc =
  381. '${DateUtil.fromMillisecondsSinceEpoch('yyyy.MM.dd', controller.memberStatusInfo?.endTimestamp ?? 0)} ${StringName.memberCardExpirationDesc}';
  382. }
  383. return Text(desc,
  384. style: TextStyle(fontSize: 12.sp, color: Colors.white60));
  385. });
  386. }
  387. Widget buildHeadBar() {
  388. return Obx(() {
  389. return Stack(
  390. children: [
  391. IgnorePointer(
  392. child: Container(
  393. color: ColorName.colorPrimary.withOpacity(controller.toolBarOpacity),
  394. child: SafeArea(
  395. child: SizedBox(
  396. width: double.infinity,
  397. height: 56.w,
  398. ),
  399. ),
  400. ),
  401. ),
  402. SafeArea(
  403. child: SizedBox(
  404. width: double.infinity,
  405. height: 56.w,
  406. child: Stack(alignment: Alignment.center, children: [
  407. Positioned(
  408. left: 12.w,
  409. child: GestureDetector(
  410. onTap: () => controller.onPopBack(),
  411. child: Assets.images.iconWhiteBack
  412. .image(width: 24.w, height: 24.w),
  413. )),
  414. Container(
  415. padding: EdgeInsets.only(left: 40.w,right: 22.w),
  416. child: buildVerticalSlideshowWidget())
  417. ]),
  418. ),
  419. ),
  420. ],
  421. );
  422. });
  423. }
  424. Widget buildVerticalSlideshowWidget() {
  425. return Row(
  426. children: [
  427. Visibility(visible: !Platform.isIOS, child: Spacer()),
  428. Container(
  429. width: 220.w,
  430. height: 24.w,
  431. decoration: BoxDecoration(
  432. color: '#1F000000'.color,
  433. borderRadius: BorderRadius.circular(100.w),
  434. ),
  435. child: Center(
  436. child: AnimatedSwitcherWidget(
  437. controller: controller.switcherController)),
  438. ),
  439. Spacer(),
  440. Visibility(
  441. visible: Platform.isIOS && controller.accountRepository.isLogin.value,
  442. child: GestureDetector(
  443. onTap: controller.clickRecoverSubscribe,
  444. child: Row(
  445. children: [
  446. Assets.images.iconAppleRecoverSubscribe.image(width: 14.w,height: 14.w),
  447. Text(StringName.appleRecoverSubscribeTxt,
  448. style: TextStyle(
  449. fontSize: 12.sp,
  450. color: ColorName.white,
  451. fontWeight: FontWeight.w500)),
  452. ],
  453. ),
  454. )
  455. )
  456. ],
  457. );
  458. }
  459. Widget buildPrivacyPolicyView() {
  460. return Padding(
  461. padding: EdgeInsets.only(left: 12.w),
  462. child: RichText(
  463. text: TextSpan(
  464. style: TextStyle(fontSize: 12.sp, color: ColorName.black40),
  465. children: [
  466. TextSpan(text: '购买前请先阅读'),
  467. TextSpan(
  468. recognizer: TapGestureRecognizer()
  469. ..onTap = () {
  470. controller.onPrivacyPolicyClick();
  471. },
  472. text: '隐私政策',
  473. style: TextStyle(
  474. color: ColorName.black60,
  475. decoration: TextDecoration.underline)),
  476. TextSpan(text: '&'),
  477. TextSpan(
  478. recognizer: TapGestureRecognizer()
  479. ..onTap = () {
  480. controller.onTermOfServiceClick();
  481. },
  482. text: '服务条款',
  483. style: TextStyle(
  484. color: ColorName.black60,
  485. decoration: TextDecoration.underline)),
  486. ])),
  487. );
  488. }
  489. Widget buildFunctionList() {
  490. return SizedBox(
  491. height: 80.w,
  492. child: AutoScrollListView(
  493. padding: EdgeInsets.only(left: 12.w),
  494. itemBuilder: (ctx, index) {
  495. final item = controller.funList[index];
  496. return Padding(
  497. padding: EdgeInsets.only(right: 20.w),
  498. child: Column(
  499. children: [
  500. Image.asset(item.iconPath, width: 36.w, height: 36.w),
  501. Spacer(flex: 3),
  502. Text(item.funName,
  503. style: TextStyle(
  504. fontSize: 12.8.sp,
  505. color: ColorName.black90,
  506. fontWeight: FontWeight.bold)),
  507. Spacer(flex: 2),
  508. Text(item.funDesc,
  509. style: TextStyle(
  510. fontSize: 10.6.sp,
  511. color: ColorName.black50,
  512. )),
  513. ],
  514. ),
  515. );
  516. },
  517. itemCount: controller.funList.length));
  518. }
  519. Widget buildUserEvaluateList() {
  520. return Column(
  521. children: [
  522. for (int index = 0; index < controller.evaluateList.length; index++)
  523. buildUserEvaluateItem(controller.evaluateList[index],
  524. index == controller.evaluateList.length - 1)
  525. ],
  526. );
  527. }
  528. Widget buildUserEvaluateItem(MemberEvaluateBean item, bool isLast) {
  529. return Column(
  530. crossAxisAlignment: CrossAxisAlignment.start,
  531. children: [
  532. SizedBox(height: 20.w),
  533. Row(
  534. children: [
  535. SizedBox(width: 12.w),
  536. Image.asset(item.avatarPath, width: 24.w, height: 24.w),
  537. SizedBox(width: 8.w),
  538. Text(
  539. item.userName,
  540. style: TextStyle(fontSize: 14.sp, color: Colors.black),
  541. )
  542. ],
  543. ),
  544. SizedBox(height: 1.w),
  545. Padding(
  546. padding: EdgeInsets.only(left: 44.w, right: 12.w),
  547. child: Text(
  548. item.userEvaluate,
  549. style: TextStyle(fontSize: 14.sp, color: '#BF000000'.color),
  550. )),
  551. SizedBox(height: 20.w),
  552. Visibility(
  553. child: Container(
  554. margin: EdgeInsets.only(left: 26.w),
  555. width: 288.w,
  556. color: '#21000000'.color,
  557. height: 1.w),
  558. )
  559. ],
  560. );
  561. }
  562. Widget buildPayWayView() {
  563. return Obx(() {
  564. return Visibility(
  565. visible: Platform.isIOS ? false : controller.payItemList.isNotEmpty,
  566. child: Container(
  567. margin: EdgeInsets.only(top: 7.w),
  568. child: Column(
  569. children: [
  570. for (PayItemBean item in controller.payItemList)
  571. buildPayWayItem(item)
  572. ],
  573. ),
  574. ),
  575. );
  576. });
  577. }
  578. Widget buildPayWayItem(PayItemBean item) {
  579. return GestureDetector(
  580. behavior: HitTestBehavior.translucent,
  581. onTap: () => controller.onPayWayItemClick(item),
  582. child: Obx(() {
  583. bool isSelected = controller.selectedPayWay?.id == item.id;
  584. return Container(
  585. padding: EdgeInsets.symmetric(vertical: 10.w),
  586. child: Row(
  587. children: [
  588. SizedBox(width: 12.w),
  589. Image.asset(
  590. getPaymentIconPath(
  591. payMethod: item.payMethod, payPlatform: item.payPlatform),
  592. width: 24.w,
  593. height: 24.w),
  594. SizedBox(width: 6.w),
  595. Text(item.title,
  596. style: TextStyle(fontSize: 14.sp, color: ColorName.black90)),
  597. Spacer(),
  598. Image.asset(
  599. isSelected
  600. ? Assets.images.iconCbSelected.path
  601. : Assets.images.iconCbUnSelect.path,
  602. width: 20.w,
  603. height: 20.w),
  604. SizedBox(width: 20.w),
  605. ],
  606. ),
  607. );
  608. }),
  609. );
  610. }
  611. }