store_view.dart 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. import 'package:clean/base/base_page.dart';
  2. import 'package:clean/data/bean/store_item.dart';
  3. import 'package:clean/module/browser/browser_view.dart';
  4. import 'package:clean/module/store/store_controller.dart';
  5. import 'package:clean/router/app_pages.dart';
  6. import 'package:clean/utils/expand.dart';
  7. import 'package:flutter/Material.dart';
  8. import 'package:flutter_screenutil/flutter_screenutil.dart';
  9. import 'package:get/get.dart';
  10. import 'package:intl/intl.dart';
  11. import '../../data/consts/constants.dart';
  12. import '../../resource/assets.gen.dart';
  13. class StorePage extends BasePage<StoreController> {
  14. const StorePage({super.key});
  15. static start() {
  16. Get.toNamed(RoutePath.store);
  17. }
  18. @override
  19. bool immersive() {
  20. return true;
  21. }
  22. @override
  23. bool statusBarDarkFont() => false;
  24. @override
  25. Widget buildBody(BuildContext context) {
  26. return Scaffold(
  27. backgroundColor: Colors.black,
  28. body: Stack(
  29. children: [
  30. IgnorePointer(
  31. child: Assets.images.bgStore.image(
  32. width: 360.w,
  33. ),
  34. ),
  35. SafeArea(
  36. child: Column(
  37. crossAxisAlignment: CrossAxisAlignment.start,
  38. children: [
  39. Row(
  40. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  41. crossAxisAlignment: CrossAxisAlignment.center,
  42. children: [
  43. Container(
  44. margin: EdgeInsets.only(left: 16.w, top: 14.h),
  45. child: GestureDetector(
  46. onTap: () {
  47. controller.closeBackClick();
  48. },
  49. child: Assets.images.iconStoreClose
  50. .image(width: 28.w, height: 28.w),
  51. ),
  52. ),
  53. Container(
  54. margin: EdgeInsets.only(right: 16.w, top: 17.h),
  55. child: GestureDetector(
  56. onTap: () {
  57. controller.onRestoreClick();
  58. },
  59. child: Text(
  60. 'Restore',
  61. style: TextStyle(
  62. color: Colors.white70,
  63. fontSize: 14.sp,
  64. ),
  65. ),
  66. ),
  67. ),
  68. ],
  69. ),
  70. SizedBox(
  71. height: 30.h,
  72. ),
  73. // 标题
  74. Center(
  75. child: RichText(
  76. text: TextSpan(
  77. children: [
  78. TextSpan(
  79. text: 'CLEAN ',
  80. style: TextStyle(
  81. color: "#FFDD55".color,
  82. fontSize: 20.sp,
  83. fontWeight: FontWeight.bold,
  84. ),
  85. ),
  86. TextSpan(
  87. text: 'PRO ',
  88. style: TextStyle(
  89. color: Colors.white,
  90. fontSize: 20.sp,
  91. fontWeight: FontWeight.bold,
  92. ),
  93. ),
  94. TextSpan(
  95. text: 'FREE ',
  96. style: TextStyle(
  97. color: "#FFDD55".color,
  98. fontSize: 20.sp,
  99. fontWeight: FontWeight.bold,
  100. ),
  101. ),
  102. TextSpan(
  103. text: 'UP STORAGE',
  104. style: TextStyle(
  105. color: Colors.white,
  106. fontSize: 20.sp,
  107. fontWeight: FontWeight.bold,
  108. ),
  109. ),
  110. ],
  111. ),
  112. ),
  113. ),
  114. SizedBox(
  115. height: 20.h,
  116. ),
  117. Center(
  118. child: Container(
  119. width: 328.w,
  120. height: 203.h,
  121. decoration: BoxDecoration(
  122. image: DecorationImage(
  123. image: Assets.images.bgStoreFunc.provider(),
  124. fit: BoxFit.fill,
  125. ),
  126. ),
  127. child: Column(
  128. mainAxisAlignment: MainAxisAlignment.spaceAround,
  129. children: [
  130. _buildFeatureItem(
  131. icon: Assets.images.iconStoreSimilar
  132. .image(width: 30.w, height: 30.w),
  133. title: 'One-click Remove Similar Photos',
  134. ),
  135. _buildFeatureItem(
  136. icon: Assets.images.iconStoreAi
  137. .image(width: 30.w, height: 30.w),
  138. title: 'Detect Blurry and Junk Photos',
  139. ),
  140. _buildFeatureItem(
  141. icon: Assets.images.iconStoreContacts
  142. .image(width: 30.w, height: 30.w),
  143. title: 'Merge Duplicate Contacts',
  144. ),
  145. _buildFeatureItem(
  146. icon: Assets.images.iconStorePremium
  147. .image(width: 30.w, height: 30.w),
  148. title: 'Premium Unlimited',
  149. ),
  150. ],
  151. ),
  152. ),
  153. ),
  154. SizedBox(height: 14.h),
  155. Expanded(
  156. child: Obx(() {
  157. return ListView.builder(
  158. itemCount: controller.storeItems.length,
  159. itemBuilder: (context, index) {
  160. var item = controller.storeItems[index];
  161. return _buildSubscriptionOption(
  162. item: item,
  163. );
  164. },
  165. );
  166. }),
  167. ),
  168. Center(
  169. child: Text(
  170. 'Auto-renewable.Cancel anytime.',
  171. style: TextStyle(
  172. color: Colors.white.withOpacity(0.8),
  173. fontSize: 12.sp,
  174. ),
  175. ),
  176. ),
  177. SizedBox(
  178. height: 5.h,
  179. ),
  180. Center(
  181. child: GestureDetector(
  182. onTap: () {
  183. controller.onBuyClick();
  184. },
  185. child: Container(
  186. width: 312.w,
  187. height: 48.h,
  188. decoration: BoxDecoration(
  189. color: "#0279FB".color,
  190. borderRadius: BorderRadius.all(
  191. Radius.circular(24.r),
  192. ),
  193. ),
  194. child: Center(
  195. child: Text(
  196. "CONTINUE",
  197. style: TextStyle(
  198. color: Colors.white,
  199. fontWeight: FontWeight.w700,
  200. fontSize: 16.sp,
  201. ),
  202. ),
  203. ),
  204. ),
  205. ),
  206. ),
  207. // // 底部链接
  208. SizedBox(
  209. height: 8.h,
  210. ),
  211. Row(
  212. mainAxisAlignment: MainAxisAlignment.center,
  213. children: [
  214. GestureDetector(
  215. onTap: () {
  216. BrowserPage.start(Constants.privacyPolicy);
  217. },
  218. child: Text(
  219. 'Privacy Policy',
  220. style: TextStyle(
  221. color: Colors.white.withOpacity(0.8),
  222. fontSize: 12.sp,
  223. ),
  224. ),
  225. ),
  226. Text(
  227. ' | ',
  228. style: TextStyle(
  229. color: Colors.white.withOpacity(0.8),
  230. fontSize: 12.sp,
  231. ),
  232. ),
  233. GestureDetector(
  234. onTap: () {
  235. BrowserPage.start(Constants.userAgreement);
  236. },
  237. child: Text(
  238. 'User Agreement',
  239. style: TextStyle(
  240. color: Colors.white.withOpacity(0.8),
  241. fontSize: 12.sp,
  242. ),
  243. ),
  244. ),
  245. ],
  246. ),
  247. ],
  248. ),
  249. ),
  250. ],
  251. ),
  252. );
  253. }
  254. Widget _buildFeatureItem({
  255. required Image icon,
  256. required String title,
  257. }) {
  258. return Row(
  259. children: [
  260. SizedBox(width: 15.w),
  261. icon,
  262. SizedBox(width: 8.w),
  263. Text(
  264. title,
  265. style: TextStyle(
  266. color: Colors.white,
  267. fontSize: 15.sp,
  268. fontWeight: FontWeight.w500,
  269. ),
  270. ),
  271. ],
  272. );
  273. }
  274. Widget _buildSubscriptionOption({
  275. required StoreItem item,
  276. }) {
  277. bool isSelected = controller.currentSelectedStoreItem.value?.id == item.id;
  278. bool isFreeItem = (item.freeTrialName != null);
  279. final formatter = NumberFormat.currency(
  280. symbol: '\$',
  281. decimalDigits: 2,
  282. );
  283. var amount = formatter.format(item.amount / 100);
  284. return Obx(() {
  285. bool isShowFree = isFreeItem && controller.isFree.value;
  286. return Container(
  287. height: 80.h,
  288. margin: EdgeInsets.symmetric(horizontal: 20.w),
  289. child: Stack(
  290. children: [
  291. GestureDetector(
  292. onTap: () {
  293. controller.currentSelectedStoreItem.value = item;
  294. },
  295. child: Container(
  296. margin: EdgeInsets.only(top: 12.h),
  297. height: 74.h,
  298. decoration: BoxDecoration(
  299. borderRadius: BorderRadius.circular(14.r),
  300. gradient:
  301. controller.currentSelectedStoreItem.value?.id == item.id
  302. ? LinearGradient(
  303. begin: Alignment.centerRight,
  304. end: Alignment.centerLeft,
  305. colors: [
  306. '#63CEFF'.color,
  307. '#0279FB'.color,
  308. "#047AFB".color
  309. ],
  310. )
  311. : null,
  312. border:
  313. controller.currentSelectedStoreItem.value?.id == item.id
  314. ? Border.all(color: Colors.transparent, width: 0.w)
  315. : Border.all(
  316. color: Colors.white.withOpacity(0.2),
  317. width: 2.w,
  318. ),
  319. ),
  320. child: Container(
  321. margin:
  322. controller.currentSelectedStoreItem.value?.id == item.id
  323. ? EdgeInsets.all(2.w)
  324. : null,
  325. decoration: BoxDecoration(
  326. color: "#05050D".color,
  327. borderRadius: BorderRadius.circular(13.r),
  328. ),
  329. child: Container(
  330. color: Colors.white.withOpacity(0.06),
  331. child: Stack(
  332. children: [
  333. Padding(
  334. padding: EdgeInsets.only(
  335. left: 12.w, top: 10.h, bottom: 12.h, right: 12.w),
  336. child: Row(
  337. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  338. children: [
  339. Column(
  340. mainAxisAlignment:
  341. MainAxisAlignment.spaceBetween,
  342. crossAxisAlignment: CrossAxisAlignment.start,
  343. children: [
  344. Text(
  345. isShowFree
  346. ? item.freeTrialName ?? ""
  347. : item.name,
  348. style: TextStyle(
  349. color: Colors.white,
  350. fontSize: 16.sp,
  351. fontWeight: FontWeight.bold,
  352. ),
  353. ),
  354. Text(
  355. isShowFree
  356. ? item.freeTrialPriceDesc ?? ""
  357. : item.priceDesc ?? "",
  358. style: TextStyle(
  359. color: Colors.white60,
  360. fontSize: 12.sp,
  361. ),
  362. ),
  363. ],
  364. ),
  365. Visibility(
  366. visible: !isShowFree,
  367. child: Text(
  368. amount,
  369. style: TextStyle(
  370. color: Colors.white,
  371. fontSize: 16.sp,
  372. fontWeight: FontWeight.bold,
  373. ),
  374. ),
  375. ),
  376. ],
  377. ),
  378. ),
  379. if (isSelected)
  380. Positioned(
  381. right: 0,
  382. top: 0,
  383. child: Container(
  384. padding: EdgeInsets.only(
  385. left: 8.w,
  386. right: 8.w,
  387. top: 2.h,
  388. bottom: 5.h,
  389. ),
  390. decoration: BoxDecoration(
  391. gradient: LinearGradient(
  392. begin: Alignment.centerRight,
  393. end: Alignment.centerLeft,
  394. colors: ['#63CEFF'.color, '#0279FB'.color],
  395. ),
  396. borderRadius: BorderRadius.only(
  397. topRight: Radius.circular(12.r),
  398. bottomLeft: Radius.circular(14.r),
  399. ),
  400. // border: Border.all(color: Colors.transparent, width: 2.w),
  401. ),
  402. child: Text(
  403. 'No payment now!',
  404. style: TextStyle(
  405. color: Colors.white,
  406. fontSize: 11.sp,
  407. fontWeight: FontWeight.bold,
  408. ),
  409. ),
  410. ),
  411. ),
  412. ],
  413. ),
  414. ),
  415. ),
  416. ),
  417. ),
  418. if (isShowFree)
  419. Positioned(
  420. right: 110.w,
  421. top: 0.h,
  422. child: Assets.images.iconStoreFree
  423. .image(width: 30.w, height: 28.h),
  424. ),
  425. ],
  426. ),
  427. );
  428. });
  429. }
  430. }