new_discount_page.dart 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. import 'package:flutter/src/widgets/framework.dart';
  2. import 'package:flutter_screenutil/flutter_screenutil.dart';
  3. import 'package:keyboard/base/base_page.dart';
  4. import 'package:keyboard/data/bean/goods_info.dart';
  5. import 'package:keyboard/module/store/new_discount/new_discount_controller.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:carousel_slider/carousel_slider.dart';
  8. import 'package:get/get.dart';
  9. import '../../../data/bean/character_info.dart';
  10. import '../../../data/consts/payment_type.dart';
  11. import '../../../data/consts/web_url.dart';
  12. import '../../../resource/assets.gen.dart';
  13. import '../../../resource/colors.gen.dart';
  14. import '../../../resource/string.gen.dart';
  15. import '../../../router/app_pages.dart';
  16. import '../../../utils/styles.dart';
  17. import '../../../widget/auto_scroll_list_view.dart';
  18. import '../../../widget/click_text_span.dart';
  19. class NewDiscountPage extends BasePage<NewDiscountController> {
  20. const NewDiscountPage({super.key});
  21. /// 跳转
  22. static Future<void> start() async {
  23. await Get.toNamed(RoutePath.newDiscount);
  24. }
  25. @override
  26. bool immersive() {
  27. return true;
  28. }
  29. @override
  30. backgroundColor() => Colors.white;
  31. @override
  32. Widget buildBody(BuildContext context) {
  33. return Stack(
  34. children: [
  35. Positioned(top: 0.w, child: _buildBanner()),
  36. Positioned(
  37. top: 408.w,
  38. child: Container(
  39. decoration: BoxDecoration(
  40. color: ColorName.white,
  41. borderRadius: BorderRadius.only(
  42. topLeft: Radius.circular(24.r),
  43. topRight: Radius.circular(24.r),
  44. ),
  45. ),
  46. width: 360.w,
  47. height: 392.w,
  48. child: SingleChildScrollView(
  49. child: Column(
  50. children: [
  51. SizedBox(height: 16.w),
  52. _buildGoodsCard(),
  53. SizedBox(height: 12.w),
  54. _buildPayWayCard(),
  55. SizedBox(height: 8.w),
  56. _buildSelectedDesc(),
  57. SizedBox(height: 30.w),
  58. _buildMemberCard(),
  59. SizedBox(height: 200.w),
  60. ],
  61. ),
  62. ),
  63. ),
  64. ),
  65. Positioned(top: 0, left: 0, right: 0, child: _buildTitle()),
  66. Positioned(bottom: 0, left: 0, right: 0, child: _buildBuyButtonCard()),
  67. ],
  68. );
  69. }
  70. _buildTitle() {
  71. return SafeArea(
  72. child: Container(
  73. alignment: Alignment.centerLeft,
  74. padding: EdgeInsets.only(top: 16.h, left: 16.w, right: 16.w),
  75. child: Row(
  76. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  77. children: [
  78. GestureDetector(
  79. onTap: controller.clickBack,
  80. child: Assets.images.iconStoreBack.image(
  81. width: 32.w,
  82. height: 32.w,
  83. ),
  84. ),
  85. ],
  86. ),
  87. ),
  88. );
  89. }
  90. Widget _buildBanner() {
  91. return SizedBox(
  92. width: 360.w,
  93. child: Stack(
  94. children: [
  95. CarouselSlider(
  96. carouselController: controller.carouselSliderController,
  97. options: CarouselOptions(
  98. height: 438.w,
  99. viewportFraction: 1,
  100. initialPage: 0,
  101. enableInfiniteScroll: true,
  102. reverse: false,
  103. autoPlay: true,
  104. autoPlayInterval: const Duration(seconds: 3),
  105. autoPlayAnimationDuration: const Duration(milliseconds: 500),
  106. autoPlayCurve: Curves.fastOutSlowIn,
  107. scrollDirection: Axis.horizontal,
  108. onPageChanged: (index, reason) {
  109. controller.onBannerChanged(index, reason);
  110. },
  111. ),
  112. items:
  113. controller.bannerList.map((item) {
  114. return item.image(width: 360.w, fit: BoxFit.contain);
  115. }).toList(),
  116. ),
  117. Positioned(
  118. bottom: 149.16.w,
  119. left: 31.w,
  120. child: Assets.images.iconNewDiscountCharacterTitle.image(
  121. width: 296.w,
  122. height: 39.w,
  123. ),
  124. ),
  125. Positioned(
  126. bottom: 211.13.w,
  127. left: 42.w,
  128. right: 0,
  129. child: _buildIndicator(),
  130. ),
  131. Positioned(
  132. bottom: 0,
  133. left: 0,
  134. right: 0,
  135. child: _buildAutoCharacterList(),
  136. ),
  137. ],
  138. ),
  139. );
  140. }
  141. /// 指示器
  142. _buildIndicator() {
  143. return Row(
  144. children:
  145. controller.bannerList.asMap().entries.map((entry) {
  146. return Obx(() {
  147. final isSelectedBanner =
  148. controller.currentBannerIndex == entry.key;
  149. return Row(
  150. mainAxisAlignment: MainAxisAlignment.spaceAround,
  151. children: [
  152. GestureDetector(
  153. onTap:
  154. () => controller.carouselSliderController.animateToPage(
  155. entry.key,
  156. ),
  157. child: AnimatedContainer(
  158. duration: const Duration(milliseconds: 300),
  159. margin: EdgeInsets.only(right: 5.w),
  160. height: 5.w,
  161. width: isSelectedBanner ? 10.w : 5.w,
  162. decoration: BoxDecoration(
  163. color:
  164. isSelectedBanner
  165. ? const Color(0xff483459)
  166. : const Color(0xffAFB4BF),
  167. borderRadius: BorderRadius.circular(5.r),
  168. ),
  169. ),
  170. ),
  171. ],
  172. );
  173. });
  174. }).toList(),
  175. );
  176. }
  177. Widget _buildAutoCharacterList() {
  178. return Obx(() {
  179. if (controller.charactersList.isEmpty) {
  180. return Container();
  181. }
  182. return SizedBox(
  183. height: 130.w,
  184. child: AutoScrollListView(
  185. itemCount: (controller.charactersList.length / 2).ceil(),
  186. scrollDirection: Axis.horizontal,
  187. itemBuilder: (context, columnIndex) {
  188. int rowCount = 2;
  189. int startIndex = columnIndex * rowCount;
  190. final List<CharacterInfo> columnItems =
  191. controller.charactersList
  192. .skip(startIndex)
  193. .take(rowCount)
  194. .toList();
  195. return Column(
  196. children:
  197. columnItems.map((item) {
  198. final emoji = item.emoji ?? "";
  199. final name = item.name ?? "";
  200. return Padding(
  201. padding: EdgeInsets.symmetric(
  202. vertical: 4.w,
  203. horizontal: 4.w,
  204. ),
  205. child: Container(
  206. padding: EdgeInsets.symmetric(
  207. horizontal: 12.w,
  208. vertical: 6.w,
  209. ),
  210. decoration: ShapeDecoration(
  211. color: Colors.white,
  212. shape: RoundedRectangleBorder(
  213. side: BorderSide(
  214. width: 1.w,
  215. color: const Color(0xFFF4F1FF),
  216. ),
  217. borderRadius: BorderRadius.circular(31.r),
  218. ),
  219. ),
  220. child: Text(
  221. "$emoji$name",
  222. style: TextStyle(
  223. color: Colors.black.withAlpha(204),
  224. fontSize: 13.sp,
  225. fontWeight: FontWeight.w400,
  226. ),
  227. ),
  228. ),
  229. );
  230. }).toList(),
  231. );
  232. },
  233. ),
  234. );
  235. });
  236. }
  237. Widget _buildBuyButtonCard() {
  238. return Container(
  239. width: 360.w,
  240. padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 11.h),
  241. decoration: ShapeDecoration(
  242. color: Colors.white,
  243. shape: RoundedRectangleBorder(
  244. borderRadius: BorderRadius.only(
  245. topLeft: Radius.circular(24.r),
  246. topRight: Radius.circular(24.r),
  247. ),
  248. ),
  249. shadows: [
  250. BoxShadow(
  251. color: Color(0x99FFE498),
  252. blurRadius: 20,
  253. offset: Offset(0, 0),
  254. spreadRadius: 0,
  255. ),
  256. ],
  257. ),
  258. child: Column(
  259. children: [
  260. GestureDetector(
  261. onTap: controller.clickPayNow,
  262. child: Container(
  263. width: 328.w,
  264. height: 56.w,
  265. decoration: ShapeDecoration(
  266. shape: RoundedRectangleBorder(
  267. side: BorderSide(width: 2.w, color: const Color(0xFFFFF6C9)),
  268. borderRadius: BorderRadius.circular(30.55),
  269. ),
  270. ),
  271. child: Container(
  272. width: 328,
  273. height: 54,
  274. decoration: ShapeDecoration(
  275. gradient: LinearGradient(
  276. begin: Alignment(0.60, -0.39),
  277. end: Alignment(0.60, 0.95),
  278. colors: [
  279. const Color(0xFFF95FAC),
  280. const Color(0xFFFD4D99),
  281. const Color(0xFFFF3E75),
  282. const Color(0xFFFF0F53),
  283. ],
  284. ),
  285. shape: RoundedRectangleBorder(
  286. borderRadius: BorderRadius.circular(30.55),
  287. ),
  288. ),
  289. child: Center(
  290. child: Text(
  291. StringName.newDiscountUnlockNow,
  292. style: Styles.getTextStyleWhiteW500(17.sp),
  293. ),
  294. ),
  295. ),
  296. ),
  297. ),
  298. SizedBox(height: 11.h),
  299. _buildPrivacyTextSpan(),
  300. ],
  301. ),
  302. );
  303. }
  304. Widget _buildGoodsCard() {
  305. return Container(
  306. margin: EdgeInsets.symmetric(horizontal: 16.w),
  307. child: Column(
  308. crossAxisAlignment: CrossAxisAlignment.start,
  309. mainAxisAlignment: MainAxisAlignment.start,
  310. children: [
  311. Obx(() {
  312. final goodsList = controller.filteredGoodsList;
  313. if (goodsList.isEmpty) {
  314. return CircularProgressIndicator();
  315. }
  316. return Column(
  317. children: List.generate(goodsList.length, (index) {
  318. final item = goodsList[index];
  319. final isSelected =
  320. controller.selectedGoodsInfoItem?.id == item.id;
  321. return Column(
  322. children: [
  323. GestureDetector(
  324. onTap: () => controller.onGoodsItemClick(item),
  325. child: _buildGoodsItem(item, isSelected),
  326. ),
  327. if (index != goodsList.length - 1) SizedBox(height: 10.w),
  328. ],
  329. );
  330. }),
  331. );
  332. }),
  333. ],
  334. ),
  335. );
  336. }
  337. Widget _buildPayWayCard() {
  338. return GestureDetector(
  339. onTap: () => controller.clickPayWaySwitch(),
  340. child: Container(
  341. margin: EdgeInsets.symmetric(horizontal: 16.w),
  342. height: 36.h,
  343. padding: EdgeInsets.symmetric(horizontal: 10.w),
  344. decoration: ShapeDecoration(
  345. shape: RoundedRectangleBorder(
  346. side: BorderSide(width: 1, color: const Color(0xFFECEBE0)),
  347. borderRadius: BorderRadius.circular(10.r),
  348. ),
  349. ),
  350. child: Row(
  351. children: [
  352. Text(
  353. StringName.storePayWay,
  354. style: Styles.getTextStyleBlack204W400(14.sp),
  355. ),
  356. const Spacer(),
  357. Obx(() {
  358. if (controller.selectedPayWay == null) {
  359. return SizedBox.shrink();
  360. }
  361. return Row(
  362. children: [
  363. Image.asset(
  364. getPaymentIconPath(
  365. payMethod: controller.selectedPayWay!.payMethod,
  366. payPlatform: controller.selectedPayWay!.payPlatform,
  367. ),
  368. width: 20.w,
  369. height: 20.w,
  370. ),
  371. SizedBox(width: 4.w),
  372. Text(
  373. controller.selectedPayWay?.title ?? '',
  374. style: Styles.getTextStyleBlack204W400(14.sp),
  375. ),
  376. SizedBox(width: 6.w),
  377. Assets.images.iconStoreSwitchPay.image(
  378. width: 20.w,
  379. height: 20.w,
  380. fit: BoxFit.fill,
  381. ),
  382. ],
  383. );
  384. }),
  385. ],
  386. ),
  387. ),
  388. );
  389. }
  390. Widget _buildGoodsItem(GoodsInfo item, bool isSelected) {
  391. return Container(
  392. height: 70.w,
  393. width: 328.w,
  394. padding: EdgeInsets.symmetric(horizontal: 16.w),
  395. decoration:
  396. isSelected
  397. ? BoxDecoration(
  398. borderRadius: BorderRadius.circular(16.w),
  399. image: DecorationImage(
  400. image: Assets.images.bgNewDiscountItemSelect.provider(),
  401. fit: BoxFit.fill,
  402. ),
  403. )
  404. : BoxDecoration(
  405. borderRadius: BorderRadius.circular(16.w),
  406. image: DecorationImage(
  407. image: Assets.images.bgNewDiscountItemUnselect.provider(),
  408. fit: BoxFit.fill,
  409. ),
  410. ),
  411. child: Row(
  412. children: [
  413. Text(
  414. "¥",
  415. style: TextStyle(
  416. color:
  417. isSelected
  418. ? const Color(0xFFFF684E)
  419. : Colors.black.withAlpha(204),
  420. fontSize: 16.sp,
  421. fontWeight: FontWeight.w700,
  422. ),
  423. ),
  424. Text(
  425. '${item.amountText}',
  426. textAlign: TextAlign.center,
  427. style: TextStyle(
  428. color:
  429. isSelected
  430. ? const Color(0xFFFF684E)
  431. : Colors.black.withAlpha(204),
  432. fontSize: 29.sp,
  433. fontWeight: FontWeight.w700,
  434. ),
  435. ),
  436. SizedBox(width: 9.w),
  437. Column(
  438. crossAxisAlignment: CrossAxisAlignment.start,
  439. mainAxisAlignment: MainAxisAlignment.center,
  440. children: [
  441. Row(
  442. children: [
  443. Text(
  444. item.name,
  445. style: TextStyle(
  446. color: Colors.black.withAlpha(204),
  447. fontSize: 14.sp,
  448. fontWeight: FontWeight.w500,
  449. ),
  450. ),
  451. SizedBox(width: 4.5.w),
  452. if (item.timeLimitDesc?.isNotEmpty == true)
  453. Container(
  454. padding: EdgeInsets.symmetric(
  455. horizontal: 4.w,
  456. vertical: 2.w,
  457. ),
  458. margin: EdgeInsets.only(bottom: 4.w),
  459. decoration: ShapeDecoration(
  460. gradient: LinearGradient(
  461. begin: Alignment(0.00, 0.50),
  462. end: Alignment(1.00, 0.50),
  463. colors: [
  464. const Color(0xFFFF684E),
  465. const Color(0xFFFF4F9A),
  466. ],
  467. ),
  468. shape: RoundedRectangleBorder(
  469. borderRadius: BorderRadius.only(
  470. topLeft: Radius.circular(10.50),
  471. topRight: Radius.circular(10.50),
  472. bottomRight: Radius.circular(10.50),
  473. ),
  474. ),
  475. ),
  476. child: Text(
  477. item.timeLimitDesc!,
  478. style: TextStyle(
  479. color: Colors.white,
  480. fontSize: 12.sp,
  481. fontWeight: FontWeight.w500,
  482. ),
  483. ),
  484. ),
  485. ],
  486. ),
  487. Text(
  488. item.priceDesc,
  489. style: TextStyle(
  490. color: Colors.black.withAlpha(153),
  491. fontSize: 12.sp,
  492. fontWeight: FontWeight.w400,
  493. ),
  494. ),
  495. ],
  496. ),
  497. Spacer(),
  498. isSelected
  499. ? Assets.images.iconCustomCharacterAddDialogSelect.image(
  500. width: 20.w,
  501. height: 20.w,
  502. fit: BoxFit.contain,
  503. )
  504. : Container(
  505. width: 20.w,
  506. height: 20.w,
  507. decoration: ShapeDecoration(
  508. color: Colors.white,
  509. shape: OvalBorder(
  510. side: BorderSide(
  511. width: 2.w,
  512. color: const Color(0xFFE6E6E6),
  513. ),
  514. ),
  515. ),
  516. ),
  517. ],
  518. ),
  519. );
  520. }
  521. _buildSelectedDesc() {
  522. return Obx(() {
  523. return Container(
  524. margin: EdgeInsets.symmetric(horizontal: 16.w),
  525. child: Row(
  526. children: [
  527. Text(
  528. controller.selectedGoodsInfoItem?.selectDesc ?? "",
  529. style: TextStyle(
  530. color: Colors.black.withAlpha(102),
  531. fontSize: 12.sp,
  532. fontWeight: FontWeight.w400,
  533. ),
  534. ),
  535. ],
  536. ),
  537. );
  538. });
  539. }
  540. Widget _buildMemberCard() {
  541. return Container(
  542. margin: EdgeInsets.symmetric(horizontal: 16.w),
  543. child: Column(
  544. mainAxisAlignment: MainAxisAlignment.start,
  545. crossAxisAlignment: CrossAxisAlignment.start,
  546. children: [
  547. Assets.images.iconNewDiscountMembershipCardTitle.image(
  548. width: 79.w,
  549. height: 24.w,
  550. ),
  551. SizedBox(height: 6.w),
  552. Text(
  553. StringName.newDiscountMemberCardDesc,
  554. style: TextStyle(
  555. color: Colors.black.withValues(alpha: 87),
  556. fontSize: 11.sp,
  557. fontWeight: FontWeight.w400,
  558. ),
  559. ),
  560. SizedBox(height: 8.w),
  561. _buildMemberCardItem(),
  562. ],
  563. ),
  564. );
  565. }
  566. // 卡片权益
  567. _buildMemberCardItem() {
  568. return SizedBox(
  569. height: 80.w,
  570. child: AutoScrollListView(
  571. scrollDirection: Axis.horizontal,
  572. itemBuilder: (context, index) {
  573. final item = controller.memberCardList[index];
  574. return Container(
  575. margin: EdgeInsets.only(right: 8.w),
  576. width: 140.w,
  577. height: 65.w,
  578. decoration: ShapeDecoration(
  579. gradient: item.gradient,
  580. shape: RoundedRectangleBorder(
  581. side: BorderSide(width: 1.w, color: Colors.black.withAlpha(8)),
  582. borderRadius: BorderRadius.circular(20.r),
  583. ),
  584. ),
  585. child: Row(
  586. mainAxisAlignment: MainAxisAlignment.center,
  587. crossAxisAlignment: CrossAxisAlignment.center,
  588. children: [
  589. item.imageUrl.image(width: 28.w, height: 27.62.w),
  590. SizedBox(width: 8.w),
  591. Column(
  592. crossAxisAlignment: CrossAxisAlignment.start,
  593. mainAxisAlignment: MainAxisAlignment.center,
  594. children: [
  595. Text(
  596. item.title,
  597. style: TextStyle(
  598. color: Colors.black.withAlpha(204),
  599. fontSize: 12.sp,
  600. fontWeight: FontWeight.w700,
  601. ),
  602. ),
  603. SizedBox(height: 6.w),
  604. Text(
  605. item.desc,
  606. style: TextStyle(
  607. color: Colors.black.withAlpha(128),
  608. fontSize: 10.sp,
  609. fontWeight: FontWeight.w400,
  610. ),
  611. ),
  612. ],
  613. ),
  614. ],
  615. ),
  616. );
  617. },
  618. itemCount: controller.memberCardList.length,
  619. ),
  620. );
  621. }
  622. Widget _buildPrivacyTextSpan() {
  623. return Row(
  624. mainAxisAlignment: MainAxisAlignment.center,
  625. children: [
  626. Obx(() {
  627. return GestureDetector(
  628. behavior: HitTestBehavior.opaque,
  629. onTap: () {
  630. controller.isAgree.value = !controller.isAgree.value;
  631. },
  632. child:
  633. controller.isAgree.value
  634. ? Assets.images.iconStoreAgreePrivacy.image(
  635. width: 16.w,
  636. height: 16.w,
  637. )
  638. : SizedBox(
  639. child: Container(
  640. padding: EdgeInsets.all(1.w),
  641. width: 16.w,
  642. height: 16.w,
  643. child: Container(
  644. decoration: BoxDecoration(
  645. shape: BoxShape.circle,
  646. border: Border.all(
  647. color: Colors.black.withAlpha(153),
  648. width: 1.w,
  649. ),
  650. ),
  651. ),
  652. ),
  653. ),
  654. );
  655. }),
  656. Text.rich(
  657. TextSpan(
  658. children: [
  659. TextSpan(
  660. text: StringName.textSpanIHaveReadAndAgree,
  661. style: TextStyle(
  662. color: Colors.black.withAlpha(153),
  663. fontSize: 10.sp,
  664. fontWeight: FontWeight.w400,
  665. ),
  666. ),
  667. ClickTextSpan(
  668. text: StringName.textSpanPrivacyPolicy,
  669. url: WebUrl.privacyPolicy,
  670. ),
  671. TextSpan(
  672. text: StringName.textSpanAnd,
  673. style: TextStyle(
  674. color: Colors.black.withAlpha(153),
  675. fontSize: 10.sp,
  676. fontWeight: FontWeight.w400,
  677. ),
  678. ),
  679. ClickTextSpan(
  680. text: StringName.textSpanServiceTerms,
  681. url: WebUrl.serviceAgreement,
  682. ),
  683. ],
  684. ),
  685. ),
  686. ],
  687. );
  688. }
  689. }