member_activity_page.dart 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  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/data/bean/goods_bean.dart';
  11. import 'package:location/module/member/activity/member_activity_banner_widget.dart';
  12. import 'package:location/resource/assets.gen.dart';
  13. import 'package:location/resource/colors.gen.dart';
  14. import 'package:location/resource/string.gen.dart';
  15. import 'package:location/utils/common_expand.dart';
  16. import 'package:location/utils/project_expand.dart';
  17. import '../../../resource/fonts.gen.dart';
  18. import '../../../router/app_pages.dart';
  19. import '../../../widget/activity_countdown_txt_view.dart';
  20. import '../../../widget/activity_countdown_view.dart';
  21. import '../../../widget/shimmer_effect.dart';
  22. import 'member_activity_controller.dart';
  23. class MemberActivityPage extends BasePage<MemberActivityController> {
  24. const MemberActivityPage({super.key});
  25. static void start() {
  26. Get.toNamed(RoutePath.memberActivity);
  27. }
  28. @override
  29. bool immersive() {
  30. return true;
  31. }
  32. @override
  33. bool statusBarDarkFont() {
  34. return false;
  35. }
  36. @override
  37. Widget buildBody(BuildContext context) {
  38. return Stack(
  39. children: [
  40. buildActivityContent(),
  41. buildToolbar(),
  42. buildMemberBottomView()
  43. ],
  44. );
  45. }
  46. Widget buildToolbar() {
  47. return SafeArea(
  48. child: SizedBox(
  49. width: double.infinity,
  50. height: 56.w,
  51. child: Stack(
  52. children: [
  53. Positioned(
  54. left: 10,
  55. top: 0,
  56. bottom: 0,
  57. child: GestureDetector(
  58. onTap: controller.onBack,
  59. child: Container(
  60. padding: EdgeInsets.only(
  61. left: 10.w, right: 12.w, top: 10.w, bottom: 10.w),
  62. child: Assets.images.iconMemberActivityClose
  63. .image(width: 8.w)),
  64. ),
  65. ),
  66. Center(
  67. child: Text(
  68. StringName.memberActivityTitle,
  69. style: TextStyle(
  70. fontSize: 18.sp,
  71. color: Colors.white,
  72. fontWeight: FontWeight.bold),
  73. ),
  74. ),
  75. Positioned(
  76. right: 10.w,
  77. top: 0,
  78. bottom: 0,
  79. child: Obx(() {
  80. return Visibility(
  81. visible: controller.accountRepository.isLogin.value &&
  82. Platform.isIOS,
  83. child: GestureDetector(
  84. onTap: controller.onRecoverClick,
  85. child: Row(
  86. children: [
  87. Assets.images.iconAppleRecoverSubscribe
  88. .image(width: 14.w, height: 14.w),
  89. SizedBox(width: 1.w),
  90. Text(StringName.appleRecoverSubscribeTxt,
  91. style: TextStyle(
  92. fontSize: 11.sp,
  93. color: Colors.white,
  94. fontWeight: FontWeight.bold))
  95. ],
  96. ),
  97. ),
  98. );
  99. }),
  100. )
  101. ],
  102. ),
  103. ),
  104. );
  105. }
  106. Widget buildActivityContent() {
  107. return SizedBox(
  108. width: double.infinity,
  109. height: double.infinity,
  110. child: SingleChildScrollView(
  111. child: Stack(
  112. children: [
  113. Assets.images.bgMemberActivity
  114. .image(width: double.infinity, fit: BoxFit.cover),
  115. SafeArea(
  116. child: Column(
  117. crossAxisAlignment: CrossAxisAlignment.start,
  118. children: [
  119. SizedBox(height: 83.w),
  120. Container(
  121. margin: EdgeInsets.only(left: 16.w),
  122. child: Assets.images.iconMemberActivityCountdown
  123. .image(height: 22.w)),
  124. SizedBox(height: 10.w),
  125. Container(
  126. margin: EdgeInsets.only(left: 16.w, bottom: 16.w),
  127. child: Assets.images.imgMemberActivityFavourableTxt
  128. .image(height: 20.w),
  129. ),
  130. buildGoodsContainer(),
  131. ],
  132. ),
  133. )
  134. ],
  135. ),
  136. ),
  137. );
  138. }
  139. Widget buildGoodsContainer() {
  140. return Container(
  141. width: double.infinity,
  142. decoration: BoxDecoration(
  143. gradient: LinearGradient(
  144. stops: const [0.0, 0.16],
  145. colors: ['#E4E4FF'.color, Colors.white],
  146. begin: Alignment.topCenter,
  147. end: Alignment.bottomCenter),
  148. borderRadius: BorderRadius.only(
  149. topLeft: Radius.circular(20.w), topRight: Radius.circular(20.w)),
  150. ),
  151. child: Stack(
  152. children: [
  153. Assets.images.bgMemberActivityContainer
  154. .image(width: double.infinity, fit: BoxFit.fill),
  155. Column(
  156. crossAxisAlignment: CrossAxisAlignment.start,
  157. children: [
  158. SizedBox(height: 14.w),
  159. buildFunTitleView(),
  160. buildMemberActivityFunctionIntroduction(),
  161. SizedBox(height: 8.w),
  162. buildGoodsListView(),
  163. buildPrivacyPolicyView(),
  164. SizedBox(height: 150.w),
  165. ],
  166. )
  167. ],
  168. ),
  169. );
  170. }
  171. Widget buildFunTitleView() {
  172. return Container(
  173. margin: EdgeInsets.only(left: 16.w, bottom: 18.w),
  174. child: Stack(
  175. children: [
  176. Container(
  177. width: 117.w,
  178. height: 8.w,
  179. decoration: BoxDecoration(
  180. gradient: LinearGradient(
  181. colors: ['#AA70FE'.color, '#006A2DC4'.color],
  182. begin: Alignment.centerLeft,
  183. end: Alignment.centerRight,
  184. ),
  185. borderRadius: BorderRadius.only(
  186. topLeft: Radius.circular(10.w),
  187. bottomLeft: Radius.circular(10.w))),
  188. margin: EdgeInsets.only(top: 15.5.w),
  189. ),
  190. Container(
  191. margin: EdgeInsets.only(left: 4.w),
  192. child: Text(
  193. StringName.memberActivityFunTitle,
  194. style: TextStyle(
  195. fontSize: 15.sp,
  196. color: '#202020'.color,
  197. fontWeight: FontWeight.bold),
  198. ),
  199. ),
  200. ],
  201. ),
  202. );
  203. }
  204. Widget buildPrivacyPolicyView() {
  205. return Padding(
  206. padding: EdgeInsets.only(left: 15.w),
  207. child: RichText(
  208. text: TextSpan(
  209. style: TextStyle(fontSize: 12.sp, color: ColorName.black40),
  210. children: [
  211. TextSpan(text: '购买前请先阅读'),
  212. TextSpan(
  213. recognizer: TapGestureRecognizer()
  214. ..onTap = () {
  215. controller.onPrivacyPolicyClick();
  216. },
  217. text: '隐私政策',
  218. style: TextStyle(
  219. color: ColorName.black60,
  220. decoration: TextDecoration.underline)),
  221. TextSpan(text: '&'),
  222. TextSpan(
  223. recognizer: TapGestureRecognizer()
  224. ..onTap = () {
  225. controller.onTermOfServiceClick();
  226. },
  227. text: '服务条款',
  228. style: TextStyle(
  229. color: ColorName.black60,
  230. decoration: TextDecoration.underline)),
  231. if (Platform.isIOS) TextSpan(text: '&'),
  232. if (Platform.isIOS)
  233. TextSpan(
  234. recognizer: TapGestureRecognizer()
  235. ..onTap = () {
  236. controller.onRenewalAgreementClick();
  237. },
  238. text: '会员协议',
  239. style: TextStyle(
  240. color: ColorName.black60,
  241. decoration: TextDecoration.underline)),
  242. ])),
  243. );
  244. }
  245. Widget buildCountdownSeparator() {
  246. return Container(
  247. margin: EdgeInsets.symmetric(horizontal: 2.w),
  248. child: IntrinsicHeight(
  249. child: Column(
  250. children: [
  251. Container(
  252. width: 2.w,
  253. height: 2.w,
  254. decoration: BoxDecoration(
  255. color: '#FF5656'.color,
  256. shape: BoxShape.circle,
  257. ),
  258. ),
  259. SizedBox(height: 3.w),
  260. Container(
  261. width: 2.w,
  262. height: 2.w,
  263. decoration: BoxDecoration(
  264. color: '#FF5656'.color,
  265. shape: BoxShape.circle,
  266. ),
  267. )
  268. ],
  269. ),
  270. ),
  271. );
  272. }
  273. Widget buildMemberBottomView() {
  274. return Obx(() {
  275. if (controller.memberStatusInfo != null &&
  276. controller.memberStatusInfo?.permanent == true) {
  277. return SizedBox(); // 不显示
  278. }
  279. return Align(
  280. alignment: Alignment.bottomCenter,
  281. child: Container(
  282. margin: EdgeInsets.only(bottom: 15.w, left: 12.w, right: 12.w),
  283. child: Stack(
  284. children: [
  285. Container(
  286. width: 336.w,
  287. height: 47.w,
  288. decoration: BoxDecoration(
  289. borderRadius: BorderRadius.only(
  290. topLeft: Radius.circular(30.w),
  291. topRight: Radius.circular(30.w),
  292. ),
  293. color: '#FFFED8'.color),
  294. child: Align(
  295. alignment: Alignment(0.0, -0.75),
  296. child: Row(
  297. mainAxisAlignment: MainAxisAlignment.center,
  298. children: [
  299. Text(
  300. StringName.memberActivityCountdown,
  301. style: TextStyle(
  302. fontSize: 11.sp, color: '#FF5656'.color),
  303. ),
  304. SizedBox(width: 4.w),
  305. Obx(() {
  306. return ActivityCountdownTextView(
  307. timeItemHeight: 15.w,
  308. contentPadding: EdgeInsets.zero,
  309. timeItemWidth: 16.w,
  310. textStyle: TextStyle(
  311. fontSize: 10.sp, color: Colors.white),
  312. duration: controller.activityDuration ??
  313. Duration(seconds: 0),
  314. separator: buildCountdownSeparator(),
  315. timeBgBoxDecoration: BoxDecoration(
  316. color: '#FF5656'.color,
  317. borderRadius: BorderRadius.circular(3.w),
  318. ));
  319. }),
  320. SizedBox(width: 4.w),
  321. Text(
  322. StringName.memberActivitySpeciallyPreferential,
  323. style: TextStyle(
  324. fontSize: 10.sp, color: '#FF5656'.color),
  325. )
  326. ],
  327. ),
  328. ),
  329. ),
  330. GestureDetector(
  331. onTap: controller.onBuyClick,
  332. child: Container(
  333. margin: EdgeInsets.only(top: 24.w),
  334. height: 50.w,
  335. width: 336.w,
  336. child: ShimmerEffect(
  337. image: Assets.images.imgMemberBtnShadow.provider(),
  338. shimmerWidthFactor: 0.244047619047619,
  339. duration: Duration(milliseconds: 3000),
  340. delay: Duration(milliseconds: 800),
  341. child: Container(
  342. height: 50.w,
  343. width: 336.w,
  344. alignment: Alignment.center,
  345. decoration: BoxDecoration(
  346. image: DecorationImage(
  347. image: Assets.images.iconMemberSettlementBg
  348. .provider(),
  349. fit: BoxFit.fill)),
  350. child: Text(
  351. StringName.memberVipUnlock,
  352. style: TextStyle(
  353. fontSize: 18.sp,
  354. color: '#FFF8EF'.color,
  355. fontWeight: FontWeight.bold),
  356. ),
  357. ),
  358. ),
  359. ),
  360. )
  361. ],
  362. ),
  363. ));
  364. });
  365. }
  366. Widget buildMemberActivityFunctionIntroduction() {
  367. return MemberActivityBannerWidget(
  368. bannerHeight: 145.w,
  369. viewportFraction: 0.88,
  370. itemCount: controller.funImages.length,
  371. itemBuilder: (context, index) {
  372. final image = controller.funImages[index];
  373. return Container(
  374. margin: EdgeInsets.symmetric(horizontal: 5.w),
  375. child: Image(
  376. image: image,
  377. width: double.infinity,
  378. height: double.infinity,
  379. fit: BoxFit.fill),
  380. );
  381. });
  382. }
  383. Widget buildGoodsListView() {
  384. return IntrinsicHeight(
  385. child: Obx(() {
  386. return Column(
  387. children: [
  388. for (int i = 0; i < controller.goodsList.length; i++)
  389. if (i == 0)
  390. buildFavourableGoodsView(controller.goodsList[i])
  391. else
  392. buildNormalGoodsView(controller.goodsList[i])
  393. ],
  394. );
  395. }),
  396. );
  397. }
  398. Widget buildFavourableGoodsView(GoodsBean goodsInfo) {
  399. bool isSelected = controller.selectedGoods?.id == goodsInfo.id;
  400. return GestureDetector(
  401. onTap: () {
  402. controller.onGoodsItemClick(goodsInfo);
  403. },
  404. child: Container(
  405. height: 96.w,
  406. width: double.infinity,
  407. margin: EdgeInsets.only(left: 12.w, right: 16.w, bottom: 12.w),
  408. decoration: BoxDecoration(
  409. image: DecorationImage(
  410. image: isSelected
  411. ? Assets.images.iconMemberSpecialProductsSelect.provider()
  412. : Assets.images.iconMemberSpecialProductsNormal.provider(),
  413. fit: BoxFit.fill,
  414. ),
  415. ),
  416. child: Stack(
  417. children: [
  418. Positioned(
  419. top: 10.w,
  420. bottom: 0,
  421. left: 24.w,
  422. child: Column(
  423. mainAxisAlignment: MainAxisAlignment.center,
  424. children: [
  425. RichText(
  426. text: TextSpan(
  427. style: TextStyle(
  428. color: isSelected
  429. ? '#FF5656'.color
  430. : "#323133".color,
  431. fontWeight: FontWeight.bold),
  432. children: [
  433. TextSpan(
  434. text: '¥',
  435. style: TextStyle(fontSize: 16.sp, height: 1)),
  436. TextSpan(
  437. text: goodsInfo.amount.divideBy100(),
  438. style: TextStyle(
  439. fontSize: 28.sp,
  440. height: 1,
  441. //fontFamily: FontFamily.oppoSans
  442. ))
  443. ])),
  444. Text('¥${goodsInfo.originalAmount.divideBy100()}',
  445. style: TextStyle(
  446. decoration: TextDecoration.lineThrough,
  447. decorationColor: ColorName.black40,
  448. decorationThickness: 1.0,
  449. fontSize: 12.sp,
  450. color: ColorName.black40))
  451. ],
  452. ),
  453. ),
  454. Positioned(
  455. top: 10.w,
  456. bottom: 0,
  457. left: 105.w,
  458. child: Column(
  459. crossAxisAlignment: CrossAxisAlignment.start,
  460. mainAxisAlignment: MainAxisAlignment.center,
  461. children: [
  462. Text(
  463. goodsInfo.name,
  464. style: TextStyle(
  465. fontSize: 17.sp,
  466. color: "#333333".color,
  467. fontWeight: FontWeight.bold),
  468. ),
  469. SizedBox(
  470. height: 3.w,
  471. ),
  472. Text(
  473. goodsInfo.description ?? "",
  474. style: TextStyle(
  475. fontSize: 11.sp,
  476. color: "#9191BA".color,
  477. fontWeight: FontWeight.bold),
  478. ),
  479. ],
  480. ),
  481. ),
  482. ],
  483. ),
  484. ),
  485. );
  486. }
  487. Widget buildNormalGoodsView(GoodsBean goodsInfo) {
  488. bool isSelected = controller.selectedGoods?.id == goodsInfo.id;
  489. return GestureDetector(
  490. onTap: () {
  491. controller.onGoodsItemClick(goodsInfo);
  492. },
  493. child: Container(
  494. width: double.infinity,
  495. height: 72.w,
  496. margin: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 12.w),
  497. child: Stack(
  498. children: [
  499. Container(
  500. width: double.infinity,
  501. height: double.infinity,
  502. decoration: BoxDecoration(
  503. gradient: isSelected
  504. ? LinearGradient(
  505. colors: ['#FFFFFF'.color, '#F3EAFF'.color],
  506. begin: Alignment.topLeft,
  507. end: Alignment.bottomRight,
  508. stops: const [0.4, 1.0])
  509. : null,
  510. borderRadius: BorderRadius.circular(12.w),
  511. border: Border.all(
  512. color: isSelected ? '#7A13C6'.color : '#DDDDDD'.color,
  513. width: isSelected ? 2.5.w : 1.w,
  514. ))),
  515. Stack(
  516. children: [
  517. Positioned(
  518. top: 4.w,
  519. bottom: 0,
  520. left: 20.w,
  521. child: Column(
  522. mainAxisAlignment: MainAxisAlignment.center,
  523. children: [
  524. RichText(
  525. text: TextSpan(
  526. style: TextStyle(
  527. fontFamily: FontFamily.oppoSans,
  528. color: isSelected
  529. ? '#FF5656'.color
  530. : "#323133".color,
  531. fontWeight: FontWeight.bold),
  532. children: [
  533. TextSpan(
  534. text: '¥',
  535. style: TextStyle(fontSize: 16.sp, height: 1)),
  536. TextSpan(
  537. text: goodsInfo.amount.divideBy100(),
  538. style: TextStyle(
  539. fontSize: 28.sp,
  540. height: 1,
  541. //fontFamily: FontFamily.oppoSans
  542. ))
  543. ])),
  544. Text('¥${goodsInfo.originalAmount.divideBy100()}',
  545. style: TextStyle(
  546. decoration: TextDecoration.lineThrough,
  547. decorationColor: ColorName.black40,
  548. decorationThickness: 1.0,
  549. fontSize: 12.sp,
  550. color: ColorName.black40))
  551. ],
  552. ),
  553. ),
  554. Positioned(
  555. top: 0.w,
  556. bottom: 0,
  557. left: 101.w,
  558. child: Column(
  559. crossAxisAlignment: CrossAxisAlignment.start,
  560. mainAxisAlignment: MainAxisAlignment.center,
  561. children: [
  562. Text(
  563. goodsInfo.name,
  564. style: TextStyle(
  565. fontSize: 17.sp,
  566. color: "#333333".color,
  567. fontWeight: FontWeight.bold),
  568. ),
  569. SizedBox(
  570. height: 3.w,
  571. ),
  572. Text(
  573. goodsInfo.description ?? "",
  574. style: TextStyle(
  575. fontSize: 11.sp,
  576. color: "#9191BA".color,
  577. fontWeight: FontWeight.bold),
  578. ),
  579. ],
  580. ),
  581. ),
  582. ],
  583. ),
  584. ],
  585. ),
  586. ),
  587. );
  588. }
  589. }