member_page.dart 20 KB

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