new_discount_page.dart 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  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 void start() {
  23. 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. return Column(
  314. children: List.generate(goodsList.length, (index) {
  315. final item = goodsList[index];
  316. final isSelected =
  317. controller.selectedGoodsInfoItem?.id == item.id;
  318. return Column(
  319. children: [
  320. GestureDetector(
  321. onTap: () => controller.onGoodsItemClick(item),
  322. child: _buildGoodsItem(item, isSelected),
  323. ),
  324. if (index != goodsList.length - 1) SizedBox(height: 10.w),
  325. ],
  326. );
  327. }),
  328. );
  329. }),
  330. ],
  331. ),
  332. );
  333. }
  334. Widget _buildPayWayCard() {
  335. return GestureDetector(
  336. onTap: () => controller.clickPayWaySwitch(),
  337. child: Container(
  338. margin: EdgeInsets.symmetric(horizontal: 16.w),
  339. height: 36.h,
  340. padding: EdgeInsets.symmetric(horizontal: 10.w),
  341. decoration: ShapeDecoration(
  342. shape: RoundedRectangleBorder(
  343. side: BorderSide(width: 1, color: const Color(0xFFECEBE0)),
  344. borderRadius: BorderRadius.circular(10.r),
  345. ),
  346. ),
  347. child: Row(
  348. children: [
  349. Text(
  350. StringName.storePayWay,
  351. style: Styles.getTextStyleBlack204W400(14.sp),
  352. ),
  353. const Spacer(),
  354. Obx(() {
  355. if (controller.selectedPayWay == null) {
  356. return SizedBox.shrink();
  357. }
  358. return Row(
  359. children: [
  360. Image.asset(
  361. getPaymentIconPath(
  362. payMethod: controller.selectedPayWay!.payMethod,
  363. payPlatform: controller.selectedPayWay!.payPlatform,
  364. ),
  365. width: 20.w,
  366. height: 20.w,
  367. ),
  368. SizedBox(width: 4.w),
  369. Text(
  370. controller.selectedPayWay?.title ?? '',
  371. style: Styles.getTextStyleBlack204W400(14.sp),
  372. ),
  373. SizedBox(width: 6.w),
  374. Assets.images.iconStoreSwitchPay.image(
  375. width: 20.w,
  376. height: 20.w,
  377. fit: BoxFit.fill,
  378. ),
  379. ],
  380. );
  381. }),
  382. ],
  383. ),
  384. ),
  385. );
  386. }
  387. Widget _buildGoodsItem(GoodsInfo item, bool isSelected) {
  388. return Container(
  389. height: 70.w,
  390. width: 328.w,
  391. padding: EdgeInsets.symmetric(horizontal: 16.w),
  392. decoration:
  393. isSelected
  394. ? BoxDecoration(
  395. borderRadius: BorderRadius.circular(16.w),
  396. image: DecorationImage(
  397. image: Assets.images.bgNewDiscountItemSelect.provider(),
  398. fit: BoxFit.fill,
  399. ),
  400. )
  401. : BoxDecoration(
  402. borderRadius: BorderRadius.circular(16.w),
  403. image: DecorationImage(
  404. image: Assets.images.bgNewDiscountItemUnselect.provider(),
  405. fit: BoxFit.fill,
  406. ),
  407. ),
  408. child: Row(
  409. children: [
  410. Text(
  411. "¥",
  412. style: TextStyle(
  413. color:
  414. isSelected
  415. ? const Color(0xFFFF684E)
  416. : Colors.black.withAlpha(204),
  417. fontSize: 16.sp,
  418. fontWeight: FontWeight.w700,
  419. ),
  420. ),
  421. Text(
  422. '${item.amountText}',
  423. textAlign: TextAlign.center,
  424. style: TextStyle(
  425. color:
  426. isSelected
  427. ? const Color(0xFFFF684E)
  428. : Colors.black.withAlpha(204),
  429. fontSize: 29.sp,
  430. fontWeight: FontWeight.w700,
  431. ),
  432. ),
  433. SizedBox(width: 9.w),
  434. Column(
  435. crossAxisAlignment: CrossAxisAlignment.start,
  436. mainAxisAlignment: MainAxisAlignment.center,
  437. children: [
  438. Row(
  439. children: [
  440. Text(
  441. item.name,
  442. style: TextStyle(
  443. color: Colors.black.withAlpha(204),
  444. fontSize: 14.sp,
  445. fontWeight: FontWeight.w500,
  446. ),
  447. ),
  448. SizedBox(width: 4.5.w),
  449. if (item.timeLimitDesc?.isNotEmpty == true)
  450. Container(
  451. padding: EdgeInsets.symmetric(
  452. horizontal: 4.w,
  453. vertical: 2.w,
  454. ),
  455. margin: EdgeInsets.only(bottom: 4.w),
  456. decoration: ShapeDecoration(
  457. gradient: LinearGradient(
  458. begin: Alignment(0.00, 0.50),
  459. end: Alignment(1.00, 0.50),
  460. colors: [
  461. const Color(0xFFFF684E),
  462. const Color(0xFFFF4F9A),
  463. ],
  464. ),
  465. shape: RoundedRectangleBorder(
  466. borderRadius: BorderRadius.only(
  467. topLeft: Radius.circular(10.50),
  468. topRight: Radius.circular(10.50),
  469. bottomRight: Radius.circular(10.50),
  470. ),
  471. ),
  472. ),
  473. child: Text(
  474. item.timeLimitDesc!,
  475. style: TextStyle(
  476. color: Colors.white,
  477. fontSize: 12.sp,
  478. fontWeight: FontWeight.w500,
  479. ),
  480. ),
  481. ),
  482. ],
  483. ),
  484. Text(
  485. item.priceDesc,
  486. style: TextStyle(
  487. color: Colors.black.withAlpha(153),
  488. fontSize: 12.sp,
  489. fontWeight: FontWeight.w400,
  490. ),
  491. ),
  492. ],
  493. ),
  494. Spacer(),
  495. isSelected
  496. ? Assets.images.iconCustomCharacterAddDialogSelect.image(
  497. width: 20.w,
  498. height: 20.w,
  499. fit: BoxFit.contain,
  500. )
  501. : Container(
  502. width: 20.w,
  503. height: 20.w,
  504. decoration: ShapeDecoration(
  505. color: Colors.white,
  506. shape: OvalBorder(
  507. side: BorderSide(
  508. width: 2.w,
  509. color: const Color(0xFFE6E6E6),
  510. ),
  511. ),
  512. ),
  513. ),
  514. ],
  515. ),
  516. );
  517. }
  518. _buildSelectedDesc() {
  519. return Obx(() {
  520. return Container(
  521. margin: EdgeInsets.symmetric(horizontal: 16.w),
  522. child: Row(
  523. children: [
  524. Text(
  525. controller.selectedGoodsInfoItem?.selectDesc ?? "",
  526. style: TextStyle(
  527. color: Colors.black.withAlpha(102),
  528. fontSize: 12.sp,
  529. fontWeight: FontWeight.w400,
  530. ),
  531. ),
  532. ],
  533. ),
  534. );
  535. });
  536. }
  537. Widget _buildMemberCard() {
  538. return Container(
  539. margin: EdgeInsets.symmetric(horizontal: 16.w),
  540. child: Column(
  541. mainAxisAlignment: MainAxisAlignment.start,
  542. crossAxisAlignment: CrossAxisAlignment.start,
  543. children: [
  544. Assets.images.iconNewDiscountMembershipCardTitle.image(
  545. width: 79.w,
  546. height: 24.w,
  547. ),
  548. SizedBox(height: 6.w),
  549. Text(
  550. StringName.newDiscountMemberCardDesc,
  551. style: TextStyle(
  552. color: Colors.black.withValues(alpha: 87),
  553. fontSize: 11.sp,
  554. fontWeight: FontWeight.w400,
  555. ),
  556. ),
  557. SizedBox(height: 8.w),
  558. _buildMemberCardItem(),
  559. ],
  560. ),
  561. );
  562. }
  563. // 卡片权益
  564. _buildMemberCardItem() {
  565. return SizedBox(
  566. height: 80.w,
  567. child: AutoScrollListView(
  568. scrollDirection: Axis.horizontal,
  569. itemBuilder: (context, index) {
  570. final item = controller.memberCardList[index];
  571. return Container(
  572. margin: EdgeInsets.only(right: 8.w),
  573. width: 140.w,
  574. height: 65.w,
  575. decoration: ShapeDecoration(
  576. gradient: item.gradient,
  577. shape: RoundedRectangleBorder(
  578. side: BorderSide(width: 1.w, color: Colors.black.withAlpha(8)),
  579. borderRadius: BorderRadius.circular(20.r),
  580. ),
  581. ),
  582. child: Row(
  583. mainAxisAlignment: MainAxisAlignment.center,
  584. crossAxisAlignment: CrossAxisAlignment.center,
  585. children: [
  586. item.imageUrl.image(width: 28.w, height: 27.62.w),
  587. SizedBox(width: 8.w),
  588. Column(
  589. crossAxisAlignment: CrossAxisAlignment.start,
  590. mainAxisAlignment: MainAxisAlignment.center,
  591. children: [
  592. Text(
  593. item.title,
  594. style: TextStyle(
  595. color: Colors.black.withAlpha(204),
  596. fontSize: 12.sp,
  597. fontWeight: FontWeight.w700,
  598. ),
  599. ),
  600. SizedBox(height: 6.w),
  601. Text(
  602. item.desc,
  603. style: TextStyle(
  604. color: Colors.black.withAlpha(128),
  605. fontSize: 10.sp,
  606. fontWeight: FontWeight.w400,
  607. ),
  608. ),
  609. ],
  610. ),
  611. ],
  612. ),
  613. );
  614. },
  615. itemCount: controller.memberCardList.length,
  616. ),
  617. );
  618. }
  619. Widget _buildPrivacyTextSpan() {
  620. return Row(
  621. mainAxisAlignment: MainAxisAlignment.center,
  622. children: [
  623. Obx(() {
  624. return GestureDetector(
  625. behavior: HitTestBehavior.opaque,
  626. onTap: () {
  627. controller.isAgree.value = !controller.isAgree.value;
  628. },
  629. child:
  630. controller.isAgree.value
  631. ? Assets.images.iconStoreAgreePrivacy.image(
  632. width: 16.w,
  633. height: 16.w,
  634. )
  635. : SizedBox(
  636. child: Container(
  637. padding: EdgeInsets.all(1.w),
  638. width: 16.w,
  639. height: 16.w,
  640. child: Container(
  641. decoration: BoxDecoration(
  642. shape: BoxShape.circle,
  643. border: Border.all(
  644. color: Colors.black.withAlpha(153),
  645. width: 1.w,
  646. ),
  647. ),
  648. ),
  649. ),
  650. ),
  651. );
  652. }),
  653. Text.rich(
  654. TextSpan(
  655. children: [
  656. TextSpan(
  657. text: StringName.textSpanIHaveReadAndAgree,
  658. style: TextStyle(
  659. color: Colors.black.withAlpha(153),
  660. fontSize: 10.sp,
  661. fontWeight: FontWeight.w400,
  662. ),
  663. ),
  664. ClickTextSpan(
  665. text: StringName.textSpanPrivacyPolicy,
  666. url: WebUrl.privacyPolicy,
  667. ),
  668. TextSpan(
  669. text: StringName.textSpanAnd,
  670. style: TextStyle(
  671. color: Colors.black.withAlpha(153),
  672. fontSize: 10.sp,
  673. fontWeight: FontWeight.w400,
  674. ),
  675. ),
  676. ClickTextSpan(
  677. text: StringName.textSpanServiceTerms,
  678. url: WebUrl.serviceAgreement,
  679. ),
  680. ],
  681. ),
  682. ),
  683. ],
  684. );
  685. }
  686. }