new_discount_page.dart 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  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.code=="vip_permanent")? '${item.priceDescNumber}':'${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. if (item.code=="vip_permanent")
  437. Text(
  438. item.priceDescUnit,
  439. style: TextStyle(
  440. color: Colors.black.withAlpha(153),
  441. fontSize: 12.sp,
  442. fontWeight: FontWeight.w400,
  443. ),
  444. ),
  445. SizedBox(width: 9.w),
  446. Column(
  447. crossAxisAlignment: CrossAxisAlignment.start,
  448. mainAxisAlignment: MainAxisAlignment.center,
  449. children: [
  450. Row(
  451. children: [
  452. Text(
  453. item.name,
  454. style: TextStyle(
  455. color: Colors.black.withAlpha(204),
  456. fontSize: 14.sp,
  457. fontWeight: FontWeight.w500,
  458. ),
  459. ),
  460. SizedBox(width: 4.5.w),
  461. if (item.timeLimitDesc?.isNotEmpty == true)
  462. Container(
  463. padding: EdgeInsets.symmetric(
  464. horizontal: 4.w,
  465. vertical: 2.w,
  466. ),
  467. margin: EdgeInsets.only(bottom: 4.w),
  468. decoration: ShapeDecoration(
  469. gradient: LinearGradient(
  470. begin: Alignment(0.00, 0.50),
  471. end: Alignment(1.00, 0.50),
  472. colors: [
  473. const Color(0xFFFF684E),
  474. const Color(0xFFFF4F9A),
  475. ],
  476. ),
  477. shape: RoundedRectangleBorder(
  478. borderRadius: BorderRadius.only(
  479. topLeft: Radius.circular(10.50),
  480. topRight: Radius.circular(10.50),
  481. bottomRight: Radius.circular(10.50),
  482. ),
  483. ),
  484. ),
  485. child: Text(
  486. item.timeLimitDesc!,
  487. style: TextStyle(
  488. color: Colors.white,
  489. fontSize: 12.sp,
  490. fontWeight: FontWeight.w500,
  491. ),
  492. ),
  493. ),
  494. ],
  495. ),
  496. Text(
  497. (item.code=="vip_permanent")? '${item.description}':'${item.priceDesc}',
  498. style: TextStyle(
  499. color: Colors.black.withAlpha(153),
  500. fontSize: 12.sp,
  501. fontWeight: FontWeight.w400,
  502. ),
  503. ),
  504. ],
  505. ),
  506. // Spacer(),
  507. // isSelected
  508. // ? Assets.images.iconCustomCharacterAddDialogSelect.image(
  509. // width: 20.w,
  510. // height: 20.w,
  511. // fit: BoxFit.contain,
  512. // )
  513. // : Container(
  514. // width: 20.w,
  515. // height: 20.w,
  516. // decoration: ShapeDecoration(
  517. // color: Colors.white,
  518. // shape: OvalBorder(
  519. // side: BorderSide(
  520. // width: 2.w,
  521. // color: const Color(0xFFE6E6E6),
  522. // ),
  523. // ),
  524. // ),
  525. // ),
  526. ],
  527. ),
  528. );
  529. }
  530. _buildSelectedDesc() {
  531. return Obx(() {
  532. return Container(
  533. margin: EdgeInsets.symmetric(horizontal: 16.w),
  534. child: Row(
  535. children: [
  536. Text(
  537. controller.selectedGoodsInfoItem?.selectDesc ?? "",
  538. style: TextStyle(
  539. color: Colors.black.withAlpha(102),
  540. fontSize: 12.sp,
  541. fontWeight: FontWeight.w400,
  542. ),
  543. ),
  544. ],
  545. ),
  546. );
  547. });
  548. }
  549. Widget _buildMemberCard() {
  550. return Container(
  551. margin: EdgeInsets.symmetric(horizontal: 16.w),
  552. child: Column(
  553. mainAxisAlignment: MainAxisAlignment.start,
  554. crossAxisAlignment: CrossAxisAlignment.start,
  555. children: [
  556. Assets.images.iconNewDiscountMembershipCardTitle.image(
  557. width: 79.w,
  558. height: 24.w,
  559. ),
  560. SizedBox(height: 6.w),
  561. Text(
  562. StringName.newDiscountMemberCardDesc,
  563. style: TextStyle(
  564. color: Colors.black.withValues(alpha: 87),
  565. fontSize: 11.sp,
  566. fontWeight: FontWeight.w400,
  567. ),
  568. ),
  569. SizedBox(height: 8.w),
  570. _buildMemberCardItem(),
  571. ],
  572. ),
  573. );
  574. }
  575. // 卡片权益
  576. _buildMemberCardItem() {
  577. return SizedBox(
  578. height: 80.w,
  579. child: AutoScrollListView(
  580. scrollDirection: Axis.horizontal,
  581. itemBuilder: (context, index) {
  582. final item = controller.memberCardList[index];
  583. return Container(
  584. margin: EdgeInsets.only(right: 8.w),
  585. width: 140.w,
  586. height: 65.w,
  587. decoration: ShapeDecoration(
  588. gradient: item.gradient,
  589. shape: RoundedRectangleBorder(
  590. side: BorderSide(width: 1.w, color: Colors.black.withAlpha(8)),
  591. borderRadius: BorderRadius.circular(20.r),
  592. ),
  593. ),
  594. child: Row(
  595. mainAxisAlignment: MainAxisAlignment.center,
  596. crossAxisAlignment: CrossAxisAlignment.center,
  597. children: [
  598. item.imageUrl.image(width: 28.w, height: 27.62.w),
  599. SizedBox(width: 8.w),
  600. Column(
  601. crossAxisAlignment: CrossAxisAlignment.start,
  602. mainAxisAlignment: MainAxisAlignment.center,
  603. children: [
  604. Text(
  605. item.title,
  606. style: TextStyle(
  607. color: Colors.black.withAlpha(204),
  608. fontSize: 12.sp,
  609. fontWeight: FontWeight.w700,
  610. ),
  611. ),
  612. SizedBox(height: 6.w),
  613. Text(
  614. item.desc,
  615. style: TextStyle(
  616. color: Colors.black.withAlpha(128),
  617. fontSize: 10.sp,
  618. fontWeight: FontWeight.w400,
  619. ),
  620. ),
  621. ],
  622. ),
  623. ],
  624. ),
  625. );
  626. },
  627. itemCount: controller.memberCardList.length,
  628. ),
  629. );
  630. }
  631. Widget _buildPrivacyTextSpan() {
  632. return Row(
  633. mainAxisAlignment: MainAxisAlignment.center,
  634. children: [
  635. Obx(() {
  636. return GestureDetector(
  637. behavior: HitTestBehavior.opaque,
  638. onTap: () {
  639. controller.isAgree.value = !controller.isAgree.value;
  640. },
  641. child:
  642. controller.isAgree.value
  643. ? Assets.images.iconStoreAgreePrivacy.image(
  644. width: 16.w,
  645. height: 16.w,
  646. )
  647. : SizedBox(
  648. child: Container(
  649. padding: EdgeInsets.all(1.w),
  650. width: 16.w,
  651. height: 16.w,
  652. child: Container(
  653. decoration: BoxDecoration(
  654. shape: BoxShape.circle,
  655. border: Border.all(
  656. color: Colors.black.withAlpha(153),
  657. width: 1.w,
  658. ),
  659. ),
  660. ),
  661. ),
  662. ),
  663. );
  664. }),
  665. Text.rich(
  666. TextSpan(
  667. children: [
  668. TextSpan(
  669. text: StringName.textSpanIHaveReadAndAgree,
  670. style: TextStyle(
  671. color: Colors.black.withAlpha(153),
  672. fontSize: 10.sp,
  673. fontWeight: FontWeight.w400,
  674. ),
  675. ),
  676. ClickTextSpan(
  677. text: StringName.textSpanPrivacyPolicy,
  678. url: WebUrl.privacyPolicy,
  679. ),
  680. TextSpan(
  681. text: StringName.textSpanAnd,
  682. style: TextStyle(
  683. color: Colors.black.withAlpha(153),
  684. fontSize: 10.sp,
  685. fontWeight: FontWeight.w400,
  686. ),
  687. ),
  688. ClickTextSpan(
  689. text: StringName.textSpanServiceTerms,
  690. url: WebUrl.serviceAgreement,
  691. ),
  692. ],
  693. ),
  694. ),
  695. ],
  696. );
  697. }
  698. }