discount_view.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_screenutil/flutter_screenutil.dart';
  3. import 'package:get/get.dart';
  4. import 'package:keyboard/base/base_view.dart';
  5. import 'package:keyboard/module/store/discount/discount_controller.dart';
  6. import 'package:keyboard/resource/string.gen.dart';
  7. import '../../../data/consts/payment_type.dart';
  8. import '../../../resource/assets.gen.dart';
  9. import '../../../utils/styles.dart';
  10. import '../../../widget/vertical_dots.dart';
  11. class DiscountView extends BaseView<DiscountController> {
  12. const DiscountView({super.key});
  13. @override
  14. Color backgroundColor() {
  15. return Colors.transparent;
  16. }
  17. @override
  18. Widget buildBody(BuildContext context) {
  19. return Container(
  20. clipBehavior: Clip.hardEdge,
  21. padding: EdgeInsets.only(bottom: 20.h),
  22. width: 360.w,
  23. decoration: ShapeDecoration(
  24. color: Color(0xFFF7FDAD),
  25. shape: RoundedRectangleBorder(
  26. borderRadius: BorderRadius.only(
  27. topLeft: Radius.circular(28.r),
  28. topRight: Radius.circular(28.r),
  29. ),
  30. ),
  31. ),
  32. child: Column(
  33. mainAxisAlignment: MainAxisAlignment.start,
  34. mainAxisSize: MainAxisSize.min,
  35. children: [
  36. buildTitleCard(),
  37. SizedBox(height: 20.h),
  38. buildBottomCard(),
  39. Transform.translate(
  40. offset: Offset(0, -30.h),
  41. child: GestureDetector(
  42. onTap: () {
  43. controller.clickPayNow();
  44. },
  45. child: Assets.images.gifDiscountUnlockButton.image(width: 244.h),
  46. ),
  47. ),
  48. ],
  49. ),
  50. );
  51. }
  52. //标题和多重权益
  53. Widget buildTitleCard() {
  54. return Container(
  55. width: 360.w,
  56. height: 290.h,
  57. decoration: BoxDecoration(
  58. image: DecorationImage(
  59. image: Assets.images.bgDiscountTitle.provider(),
  60. fit: BoxFit.fill,
  61. ),
  62. ),
  63. child: Stack(
  64. children: [
  65. Positioned(
  66. top: 16.h,
  67. left: 16.w,
  68. child: GestureDetector(
  69. onTap: () => controller.clickBack(),
  70. child: Assets.images.iconDiscountClose.image(
  71. width: 32.w,
  72. height: 32.w,
  73. ),
  74. ),
  75. ),
  76. Positioned(
  77. left: 0,
  78. right: 0,
  79. bottom: 0,
  80. child: Column(
  81. mainAxisAlignment: MainAxisAlignment.end,
  82. crossAxisAlignment: CrossAxisAlignment.center,
  83. children: [
  84. Transform.translate(
  85. offset: Offset(0, -3),
  86. child: ClipPath(
  87. clipper: BottomCurveClipper(),
  88. child: Container(
  89. width: 328.w,
  90. height: 150.h,
  91. padding: EdgeInsets.only(left: 15.w, right: 15.w),
  92. decoration: ShapeDecoration(
  93. gradient: LinearGradient(
  94. begin: Alignment(0.50, 0.56),
  95. end: Alignment(0.50, 1.00),
  96. colors: [Colors.white, const Color(0xFFDFFFD7)],
  97. ),
  98. shape: RoundedRectangleBorder(
  99. borderRadius: BorderRadius.circular(25),
  100. ),
  101. shadows: [
  102. BoxShadow(
  103. color: Color(0xFFC6FF32),
  104. blurRadius: 4.r,
  105. offset: Offset(0, 0),
  106. spreadRadius: 0,
  107. ),
  108. ],
  109. ),
  110. child: Column(
  111. crossAxisAlignment: CrossAxisAlignment.center,
  112. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  113. children: [
  114. Row(
  115. children: [
  116. Assets.images.iconDiscountBoxTitle.image(
  117. width: 73.w,
  118. height: 22.h,
  119. ),
  120. Text(
  121. StringName.discountDialogDesc,
  122. style: TextStyle(
  123. color: const Color(0xFF6EC1A8),
  124. fontSize: 10.sp,
  125. fontWeight: FontWeight.w400,
  126. ),
  127. ),
  128. ],
  129. ),
  130. Row(
  131. mainAxisAlignment: MainAxisAlignment.spaceAround,
  132. children: [
  133. _buildBox(
  134. colors: [
  135. const Color(0xFFBDE7FF),
  136. const Color(0xFFF3FBFF),
  137. ],
  138. image: Assets.images.iconDiscountChat,
  139. text: StringName.discountDialogChat,
  140. ),
  141. _buildBox(
  142. colors: [
  143. const Color(0xFFFFD5DC),
  144. const Color(0xFFFFFAFB),
  145. ],
  146. image: Assets.images.iconDiscountTutorial,
  147. text: StringName.discountDialogTutorial,
  148. ),
  149. _buildBox(
  150. colors: [
  151. const Color(0xFFFFECBC),
  152. const Color(0xFFFFFDDE),
  153. ],
  154. image: Assets.images.iconDiscountCharacter,
  155. text: StringName.discountDialogCharacter,
  156. ),
  157. _buildBox(
  158. colors: [
  159. const Color(0xFFD2DBFF),
  160. const Color(0xFFF1F4FF),
  161. ],
  162. image: Assets.images.iconDiscountSocial,
  163. text: StringName.discountDialogSocial,
  164. ),
  165. ],
  166. ),
  167. ],
  168. ),
  169. ),
  170. ),
  171. ),
  172. ],
  173. ),
  174. ),
  175. ],
  176. ),
  177. );
  178. }
  179. Widget buildBottomCard() {
  180. return Container(
  181. width: 330.w,
  182. decoration: ShapeDecoration(
  183. gradient: LinearGradient(
  184. begin: Alignment(0.00, 0.50),
  185. end: Alignment(1.00, 0.50),
  186. colors: [const Color(0xFFB2FB52), const Color(0xFF9CFD27)],
  187. ),
  188. shape: RoundedRectangleBorder(
  189. side: BorderSide(width: 1.w, color: Colors.white),
  190. borderRadius: BorderRadius.circular(20.r),
  191. ),
  192. ),
  193. child: Stack(
  194. children: [
  195. Positioned(
  196. child: IgnorePointer(
  197. child: Opacity(
  198. opacity: 0.4,
  199. child: Assets.images.bgDiscountContent.image(),
  200. ),
  201. ),
  202. ),
  203. Positioned(
  204. child: Column(
  205. children: [
  206. _buildDescTime(),
  207. Container(
  208. width: 330.w,
  209. decoration: ShapeDecoration(
  210. color: Colors.white,
  211. shape: RoundedRectangleBorder(
  212. borderRadius: BorderRadius.circular(20.r),
  213. ),
  214. ),
  215. child: SingleChildScrollView(
  216. child: Column(
  217. mainAxisAlignment: MainAxisAlignment.center,
  218. crossAxisAlignment: CrossAxisAlignment.center,
  219. children: [
  220. SizedBox(height: 12.h),
  221. buildGoodsItem(
  222. tagTop: true,
  223. tagTopDesc: "秒杀价",
  224. title: "测试",
  225. tagRight: true,
  226. tagRightDesc: "买断价",
  227. selected: false,
  228. onTap: () {},
  229. ),
  230. SizedBox(height: 12.h),
  231. buildGoodsItem(
  232. tagTop: true,
  233. tagTopDesc: "秒杀价",
  234. title: "测试",
  235. tagRight: true,
  236. tagRightDesc: "买断价",
  237. selected: true,
  238. onTap: () {},
  239. ),
  240. SizedBox(height: 12.h),
  241. _buildPayWayCard(),
  242. Container(
  243. padding: EdgeInsets.symmetric(horizontal: 16.w),
  244. alignment: Alignment.centerLeft,
  245. child: Text(
  246. '*首周18.8元,后续38元/周,可随时取消',
  247. style: TextStyle(
  248. color: const Color(0x99673300),
  249. fontSize: 10.sp,
  250. fontWeight: FontWeight.w400,
  251. height: 2,
  252. ),
  253. ),
  254. ),
  255. SizedBox(height: 54.h),
  256. ],
  257. ),
  258. ),
  259. ),
  260. ],
  261. ),
  262. ),
  263. ],
  264. ),
  265. );
  266. }
  267. Widget buildGoodsItem({
  268. required bool tagTop,
  269. required String? tagTopDesc,
  270. required bool tagRight,
  271. required String? tagRightDesc,
  272. required String title,
  273. required bool selected,
  274. required VoidCallback onTap,
  275. }) {
  276. return GestureDetector(
  277. onTap: onTap,
  278. child: Stack(
  279. clipBehavior: Clip.none,
  280. children: [
  281. Container(
  282. padding: EdgeInsets.symmetric(horizontal: 16.w),
  283. height: 72.h,
  284. width: 298.w,
  285. decoration: ShapeDecoration(
  286. color: selected ? Color(0xFFFFF9BB) : Color(0xFFFFFDE2),
  287. shape: RoundedRectangleBorder(
  288. side: BorderSide(
  289. width: 2.w,
  290. color: selected ? Color(0xFFFF7F14) : Color(0xFFFFFAC1),
  291. ),
  292. borderRadius: BorderRadius.circular(16.r),
  293. ),
  294. ),
  295. child: Row(
  296. children: [
  297. Text(
  298. title,
  299. style: TextStyle(
  300. fontSize: 22,
  301. fontWeight: FontWeight.bold,
  302. color: Color(0xFF663300),
  303. ),
  304. ),
  305. SizedBox(width: 8),
  306. if (tagRight == tagRight) // 只有“买断价”才显示这个标签
  307. Container(
  308. padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
  309. decoration: BoxDecoration(
  310. color: Color(0xFFFFD5A0),
  311. borderRadius: BorderRadius.circular(6.r),
  312. ),
  313. child: Text(
  314. tagRightDesc ?? '',
  315. style: TextStyle(fontSize: 12, color: Color(0xFFBB5A00)),
  316. ),
  317. ),
  318. Spacer(),
  319. Container(
  320. width: 20.w,
  321. height: 20.w,
  322. decoration: BoxDecoration(
  323. shape: BoxShape.circle,
  324. border: Border.all(
  325. color: selected ? Color(0xFFFF7F14) : Color(0xFFFEE86B),
  326. width: 2.w,
  327. ),
  328. color: selected ? Color(0xFFFF8400) : Colors.white,
  329. ),
  330. child:
  331. selected
  332. ? Icon(Icons.check, color: Colors.white, size: 16)
  333. : null,
  334. ),
  335. ],
  336. ),
  337. ),
  338. if (tagTop == true) // 只对“秒杀价”显示上面的标签
  339. Positioned(
  340. top: -6.h,
  341. child: Container(
  342. padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 2.h),
  343. decoration: BoxDecoration(
  344. image: DecorationImage(
  345. image: Assets.images.bgDiscountTagTop.provider(),
  346. fit: BoxFit.fill,
  347. ),
  348. borderRadius: BorderRadius.circular(4.r),
  349. ),
  350. child: Text(
  351. tagTopDesc ?? '',
  352. style: TextStyle(
  353. fontSize: 12.sp,
  354. color: Colors.white,
  355. fontWeight: FontWeight.w600,
  356. ),
  357. ),
  358. ),
  359. ),
  360. ],
  361. ),
  362. );
  363. }
  364. // 倒计时
  365. _buildDescTime() {
  366. return Container(
  367. padding: EdgeInsets.only(
  368. left: 16.w,
  369. right: 16.w,
  370. top: 12.h,
  371. bottom: 12.h,
  372. ),
  373. child: Row(
  374. crossAxisAlignment: CrossAxisAlignment.center,
  375. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  376. children: [
  377. Assets.images.iconDiscountSubhead.image(width: 90.w, height: 20.h),
  378. Row(
  379. children: [
  380. _buildTimeBox("00"),
  381. VerticalDots(
  382. height: 8.h,
  383. dotSize: 2.w,
  384. color: const Color(0xFF2F640B),
  385. horizontalPadding: 5.w,
  386. ),
  387. _buildTimeBox("00"),
  388. VerticalDots(
  389. height: 8.h,
  390. dotSize: 2.w,
  391. color: const Color(0xFF2F640B),
  392. horizontalPadding: 5.w,
  393. ),
  394. _buildTimeBox("00"),
  395. SizedBox(width: 6.w),
  396. Text(
  397. StringName.discountDialogEndDesc,
  398. style: TextStyle(
  399. color: const Color(0xFF2F640B),
  400. fontSize: 10.sp,
  401. fontWeight: FontWeight.w500,
  402. ),
  403. ),
  404. ],
  405. ),
  406. ],
  407. ),
  408. );
  409. }
  410. Widget _buildTimeBox(String value) {
  411. return Container(
  412. width: 18.w,
  413. height: 15.h,
  414. decoration: ShapeDecoration(
  415. color: Colors.white,
  416. shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4.r)),
  417. ),
  418. child: Center(
  419. child: Text(
  420. value,
  421. style: TextStyle(
  422. color: const Color(0xFF2F640B),
  423. fontSize: 10.sp,
  424. fontWeight: FontWeight.w500,
  425. ),
  426. ),
  427. ),
  428. );
  429. }
  430. // 功能模块
  431. Widget _buildBox({
  432. required List<Color> colors,
  433. required AssetGenImage image,
  434. required String text,
  435. }) {
  436. return Container(
  437. width: 70.w,
  438. height: 76.w,
  439. decoration: ShapeDecoration(
  440. gradient: LinearGradient(
  441. begin: Alignment(0.50, 0.00),
  442. end: Alignment(0.50, 1.00),
  443. colors: colors,
  444. ),
  445. shape: RoundedRectangleBorder(
  446. side: BorderSide(width: 1, color: Colors.white),
  447. borderRadius: BorderRadius.circular(10.r),
  448. ),
  449. ),
  450. child: Column(
  451. mainAxisAlignment: MainAxisAlignment.center,
  452. children: [
  453. image.image(width: 28.w, height: 28.w),
  454. SizedBox(height: 4.h),
  455. Text(
  456. text,
  457. style: TextStyle(
  458. color: const Color(0xFF4A4B28),
  459. fontSize: 13.sp,
  460. fontWeight: FontWeight.w500,
  461. ),
  462. ),
  463. ],
  464. ),
  465. );
  466. }
  467. Widget _buildPayWayCard() {
  468. return GestureDetector(
  469. onTap: () => controller.clickPayWaySwitch(),
  470. child: Container(
  471. height: 36.h,
  472. margin: EdgeInsets.symmetric(horizontal: 17.w),
  473. padding: EdgeInsets.symmetric(horizontal: 10.w),
  474. decoration: ShapeDecoration(
  475. shape: RoundedRectangleBorder(
  476. side: BorderSide(width: 1, color: const Color(0xFFECEBE0)),
  477. borderRadius: BorderRadius.circular(10.r),
  478. ),
  479. ),
  480. child: Row(
  481. children: [
  482. Text(
  483. StringName.storePayWay,
  484. style: Styles.getTextStyleBlack204W400(14.sp),
  485. ),
  486. const Spacer(),
  487. Obx(() {
  488. if (controller.selectedPayWay == null) {
  489. return SizedBox.shrink();
  490. }
  491. return Row(
  492. children: [
  493. Image.asset(
  494. getPaymentIconPath(
  495. payMethod: controller.selectedPayWay!.payMethod,
  496. payPlatform: controller.selectedPayWay!.payPlatform,
  497. ),
  498. width: 20.w,
  499. height: 20.w,
  500. ),
  501. SizedBox(width: 4.w),
  502. Text(
  503. controller.selectedPayWay?.title ?? '',
  504. style: Styles.getTextStyleBlack204W400(14.sp),
  505. ),
  506. SizedBox(width: 6.w),
  507. Assets.images.iconStoreSwitchPay.image(
  508. width: 20.w,
  509. height: 20.w,
  510. fit: BoxFit.fill,
  511. ),
  512. ],
  513. );
  514. }),
  515. ],
  516. ),
  517. ),
  518. );
  519. }
  520. }
  521. // 底部曲线剪裁
  522. class BottomCurveClipper extends CustomClipper<Path> {
  523. @override
  524. Path getClip(Size size) {
  525. Path path = Path();
  526. path.lineTo(0, size.height - 20);
  527. path.quadraticBezierTo(
  528. size.width / 2,
  529. size.height,
  530. size.width,
  531. size.height - 20,
  532. );
  533. path.lineTo(size.width, 0);
  534. path.close();
  535. return path;
  536. }
  537. @override
  538. bool shouldReclip(CustomClipper<Path> oldClipper) => false;
  539. }