new_discount_page.dart 22 KB

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