member_page.dart 23 KB

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