store_view.dart 16 KB

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