home_view.dart 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  1. import 'dart:io';
  2. import 'package:clean/base/base_view.dart';
  3. import 'package:clean/module/home/home_controller.dart';
  4. import 'package:clean/module/image_picker/image_picker_util.dart';
  5. import 'package:clean/resource/assets.gen.dart';
  6. import 'package:clean/resource/string.gen.dart';
  7. import 'package:clean/router/app_pages.dart';
  8. import 'package:clean/utils/image_util.dart';
  9. import 'package:get/get.dart';
  10. import 'package:flutter/Material.dart';
  11. import 'package:flutter_screenutil/flutter_screenutil.dart';
  12. import 'package:lottie/lottie.dart';
  13. import 'package:syncfusion_flutter_charts/charts.dart';
  14. import 'package:wechat_assets_picker/wechat_assets_picker.dart';
  15. class HomePage extends BaseView<HomeController> {
  16. const HomePage({super.key});
  17. @override
  18. backgroundColor() {
  19. return Color(0xFF05050D);
  20. }
  21. @override
  22. viewHeight() {
  23. return double.infinity;
  24. }
  25. @override
  26. Widget buildBody(BuildContext context) {
  27. // TODO: implement buildBody
  28. return Stack(
  29. children: [
  30. SafeArea(
  31. child: SingleChildScrollView(
  32. child: Column(
  33. children: [
  34. titleCard(),
  35. storageCard(),
  36. similarCard(),
  37. quickPhotoCard(),
  38. peopleCard(),
  39. locationsCard(),
  40. screenshotsAndBlurryCard(),
  41. SizedBox(height: 40.h),
  42. ],
  43. )),
  44. ),
  45. IgnorePointer(
  46. child: Assets.images.bgHome.image(
  47. width: 360.w,
  48. ),
  49. ),
  50. ],
  51. );
  52. }
  53. Widget titleCard() {
  54. return Padding(
  55. padding: EdgeInsets.only(top: 0.h, left: 16.w, right: 16.w),
  56. child: Row(
  57. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  58. children: [
  59. Text(
  60. StringName.appName,
  61. style: TextStyle(
  62. color: Colors.white,
  63. fontSize: 24.sp,
  64. fontWeight: FontWeight.w900,
  65. ),
  66. ),
  67. GestureDetector(
  68. onTap: controller.titleVipClick,
  69. child: Assets.images.iconHomeTitleVip
  70. .image(width: 60.8.w, height: 20.h),
  71. ),
  72. ],
  73. ),
  74. );
  75. }
  76. Widget storageCard() {
  77. return Container(
  78. margin: EdgeInsets.only(top: 20.h),
  79. padding: EdgeInsets.symmetric(horizontal: 16.w),
  80. width: 328.w,
  81. height: 146.h,
  82. decoration: ShapeDecoration(
  83. color: Colors.white.withValues(alpha: 0.10),
  84. shape: RoundedRectangleBorder(
  85. borderRadius: BorderRadius.circular(14.r),
  86. ),
  87. ),
  88. child: Row(
  89. children: [
  90. circularChartCard(),
  91. SizedBox(
  92. width: 14.w,
  93. ),
  94. storageInfoCard(),
  95. ],
  96. ));
  97. }
  98. Widget circularChartCard() {
  99. return SizedBox(
  100. width: 120.00.w,
  101. child: Obx(() {
  102. return SfCircularChart(
  103. series: <CircularSeries>[
  104. DoughnutSeries<PieData, String>(
  105. dataSource: [
  106. PieData('photo Space', controller.photoSpacePercentage,
  107. Colors.blue),
  108. PieData(
  109. 'Used Space',
  110. controller.usedSpacePercentage -
  111. controller.photoSpacePercentage,
  112. Colors.red),
  113. PieData(
  114. 'Unused Space',
  115. controller.isStorageScanned.value
  116. ? controller.freeSpacePercentage
  117. : 100,
  118. Colors.white.withValues(alpha: 0.10000000149011612),
  119. ),
  120. ],
  121. xValueMapper: (PieData data, _) => data.label,
  122. yValueMapper: (PieData data, _) => data.value,
  123. pointColorMapper: (PieData data, _) => data.color,
  124. cornerStyle: CornerStyle.bothFlat,
  125. radius: '100%',
  126. // 设置饼图的半径
  127. innerRadius: '80%',
  128. // 设置饼图的内半径
  129. startAngle: 0,
  130. // 设置开始角度
  131. endAngle: 360, // 设置结束角度
  132. ),
  133. ],
  134. annotations: <CircularChartAnnotation>[
  135. CircularChartAnnotation(
  136. widget: Container(
  137. child: Column(
  138. mainAxisSize: MainAxisSize.min,
  139. crossAxisAlignment: CrossAxisAlignment.center,
  140. children: [
  141. Row(
  142. mainAxisAlignment: MainAxisAlignment.center,
  143. crossAxisAlignment: CrossAxisAlignment.end,
  144. children: [
  145. Obx(() {
  146. return Text(
  147. controller.isStorageScanned.value
  148. ? controller.usedSpacePercentage
  149. .toStringAsFixed(0)
  150. : "0",
  151. textAlign: TextAlign.end,
  152. style: TextStyle(
  153. color: Colors.white
  154. .withValues(alpha: 0.8999999761581421),
  155. fontSize: 30.sp,
  156. height: 1,
  157. fontWeight: FontWeight.w400,
  158. ),
  159. );
  160. }),
  161. Text(
  162. '%',
  163. textAlign: TextAlign.end,
  164. style: TextStyle(
  165. color: Colors.white
  166. .withValues(alpha: 0.8999999761581421),
  167. fontSize: 14.87.r,
  168. fontWeight: FontWeight.w500,
  169. ),
  170. ),
  171. ],
  172. ),
  173. Text(
  174. controller.isStorageScanned.value ? 'used' : 'Scanning...',
  175. textAlign: TextAlign.center,
  176. style: TextStyle(
  177. color: Colors.white.withValues(alpha: 0.6000000238418579),
  178. fontSize:
  179. controller.isStorageScanned.value ? 14.87.sp : 12.sp,
  180. height: 1,
  181. fontWeight: FontWeight.w500,
  182. ),
  183. )
  184. ],
  185. )),
  186. horizontalAlignment: ChartAlignment.center,
  187. verticalAlignment: ChartAlignment.center,
  188. radius: '0%',
  189. ),
  190. ],
  191. );
  192. }),
  193. );
  194. }
  195. Widget storageInfoCard() {
  196. return Column(
  197. crossAxisAlignment: CrossAxisAlignment.start,
  198. children: [
  199. SizedBox(
  200. height: 24.h,
  201. ),
  202. Text(
  203. 'Storage Used',
  204. style: TextStyle(
  205. color: Colors.white,
  206. fontSize: 16.sp,
  207. fontWeight: FontWeight.w500,
  208. ),
  209. ),
  210. Row(
  211. children: [
  212. Obx(() {
  213. return Text.rich(
  214. TextSpan(
  215. children: [
  216. TextSpan(
  217. text: ImagePickerUtil.formatSize(
  218. controller.usedSpace.value),
  219. style: TextStyle(
  220. color: Color(0xFFFC4C4F),
  221. fontSize: 13.sp,
  222. fontWeight: FontWeight.w400,
  223. ),
  224. ),
  225. TextSpan(
  226. text: 'GB',
  227. style: TextStyle(
  228. color: Color(0xFFFC4C4F),
  229. fontSize: 13.sp,
  230. fontWeight: FontWeight.w500,
  231. ),
  232. ),
  233. ],
  234. ),
  235. textAlign: TextAlign.center,
  236. );
  237. }),
  238. Text(
  239. ' / ',
  240. style: TextStyle(
  241. color: Colors.white.withValues(alpha: 0.6000000238418579),
  242. fontSize: 13.sp,
  243. fontWeight: FontWeight.w500,
  244. ),
  245. ),
  246. Obx(() {
  247. return Text.rich(
  248. TextSpan(
  249. children: [
  250. TextSpan(
  251. text: ImagePickerUtil.formatSize(
  252. controller.totalSpace.value),
  253. style: TextStyle(
  254. color:
  255. Colors.white.withValues(alpha: 0.6000000238418579),
  256. fontSize: 13.sp,
  257. fontWeight: FontWeight.w400,
  258. ),
  259. ),
  260. TextSpan(
  261. text: 'GB',
  262. style: TextStyle(
  263. color:
  264. Colors.white.withValues(alpha: 0.6000000238418579),
  265. fontSize: 13.sp,
  266. fontWeight: FontWeight.w500,
  267. ),
  268. ),
  269. ],
  270. ),
  271. textAlign: TextAlign.center,
  272. );
  273. }),
  274. ],
  275. ),
  276. SizedBox(
  277. height: 10.h,
  278. ),
  279. Row(
  280. crossAxisAlignment: CrossAxisAlignment.start,
  281. children: [
  282. Container(
  283. margin: EdgeInsets.only(top: 7.h),
  284. width: 6.w,
  285. height: 6.h,
  286. decoration: ShapeDecoration(
  287. color: Color(0xFF0279FB),
  288. shape: OvalBorder(),
  289. ),
  290. ),
  291. SizedBox(
  292. width: 3.5.w,
  293. ),
  294. Column(
  295. crossAxisAlignment: CrossAxisAlignment.start,
  296. children: [
  297. Text(
  298. 'Photos',
  299. textAlign: TextAlign.start,
  300. style: TextStyle(
  301. color: Colors.white,
  302. fontSize: 14.sp,
  303. fontWeight: FontWeight.w500,
  304. ),
  305. ),
  306. Obx(() {
  307. return Text(
  308. '${controller.photoSpaceStr}',
  309. textAlign: TextAlign.start,
  310. style: TextStyle(
  311. color: Colors.white.withValues(alpha: 0.6499999761581421),
  312. fontSize: 13.sp,
  313. fontWeight: FontWeight.w400,
  314. ),
  315. );
  316. }),
  317. ],
  318. ),
  319. SizedBox(
  320. width: 10.w,
  321. ),
  322. Container(
  323. margin: EdgeInsets.only(top: 7.h),
  324. width: 6.w,
  325. height: 6.h,
  326. decoration: ShapeDecoration(
  327. color: Color(0xFFEE4933),
  328. shape: OvalBorder(),
  329. ),
  330. ),
  331. SizedBox(
  332. width: 3.5.w,
  333. ),
  334. Column(
  335. crossAxisAlignment: CrossAxisAlignment.start,
  336. children: [
  337. Text(
  338. 'iphone',
  339. textAlign: TextAlign.start,
  340. style: TextStyle(
  341. color: Colors.white,
  342. fontSize: 14.sp,
  343. fontWeight: FontWeight.w500,
  344. ),
  345. ),
  346. Obx(() {
  347. return Text(
  348. '${controller.usedSpace.toStringAsFixed(1)} GB',
  349. textAlign: TextAlign.start,
  350. style: TextStyle(
  351. color: Colors.white.withValues(alpha: 0.6499999761581421),
  352. fontSize: 13.sp,
  353. fontWeight: FontWeight.w400,
  354. ),
  355. );
  356. }),
  357. ],
  358. ),
  359. ],
  360. ),
  361. ],
  362. );
  363. }
  364. Widget similarCard() {
  365. return GestureDetector(onTap: () {
  366. controller.similarCleanClick();
  367. },
  368. child: Container(
  369. width: 328.w,
  370. height: 155.h,
  371. margin: EdgeInsets.only(top: 20.h),
  372. padding: EdgeInsets.symmetric(horizontal: 16.w),
  373. decoration: ShapeDecoration(
  374. color: Colors.white.withValues(alpha: 0.12),
  375. shape: RoundedRectangleBorder(
  376. borderRadius: BorderRadius.circular(16.r),
  377. ),
  378. ),
  379. child: Column(
  380. crossAxisAlignment: CrossAxisAlignment.start,
  381. children: [
  382. SizedBox(height: 12.h),
  383. Row(
  384. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  385. children: [
  386. Column(
  387. crossAxisAlignment: CrossAxisAlignment.start,
  388. children: [
  389. Text(
  390. 'Similar',
  391. style: TextStyle(
  392. color: Colors.white,
  393. fontSize: 16.sp,
  394. fontWeight: FontWeight.w700,
  395. ),
  396. ),
  397. Obx(() {
  398. return Text.rich(
  399. TextSpan(
  400. children: [
  401. TextSpan(
  402. text: "${ImagePickerUtil.similarPhotoCount.value}",
  403. style: TextStyle(
  404. color: Colors.white,
  405. fontSize: 12.sp,
  406. fontWeight: FontWeight.w400,
  407. ),
  408. ),
  409. TextSpan(
  410. text: ' duplicate photos detected',
  411. style: TextStyle(
  412. color: Colors.white
  413. .withValues(alpha: 0.800000011920929),
  414. fontSize: 12.sp,
  415. fontWeight: FontWeight.w400,
  416. ),
  417. ),
  418. ],
  419. ),
  420. );
  421. }),
  422. ],
  423. ),
  424. Obx(() {
  425. return CleanUpButton(
  426. label:
  427. !controller.isScanned.value ? 'Scanning...' : 'Clean up',
  428. size: ImagePickerUtil.formatFileSize(
  429. ImagePickerUtil.similarPhotosSize.value),
  430. onTap: () {
  431. controller.similarCleanClick();
  432. },
  433. );
  434. }),
  435. ],
  436. ),
  437. // SizedBox(height: 19.h),
  438. Spacer(),
  439. Obx(() {
  440. return Row(
  441. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  442. children: List.generate(4, (index) {
  443. var image = Assets.images.iconHomeNoPhoto.image(
  444. width: 70.w * 0.45,
  445. height: 70.w * 0.45,
  446. );
  447. if (controller.similarPhotos.length > index) {
  448. image = AssetEntityImage(
  449. width: 70.w,
  450. height: 70.w,
  451. controller.similarPhotos[index],
  452. isOriginal: false,
  453. thumbnailSize: const ThumbnailSize.square(300),
  454. fit: BoxFit.cover,
  455. errorBuilder: (context, error, stackTrace) {
  456. return Assets.images.iconHomeNoPhoto.image(
  457. width: 70.w * 0.45,
  458. height: 70.w * 0.45,
  459. );
  460. });
  461. }
  462. return ImageContainer(
  463. size: 70.w,
  464. image: Opacity(
  465. opacity: 0.22,
  466. child: Lottie.asset(Assets.anim.animNoPhoto,
  467. repeat: true, width: 100.w, height: 100.w),
  468. ),
  469. // AssetEntityImage(
  470. // width: 70.w,
  471. // height: 70.w,
  472. // controller.similarPhotos[index],
  473. // isOriginal: false,
  474. // thumbnailSize: const ThumbnailSize.square(300),
  475. // fit: BoxFit.cover,
  476. // errorBuilder: (context, error, stackTrace) {
  477. // return Assets.images.iconHomeNoPhoto.image(
  478. // width: 70.w * 0.45,
  479. // height: 70.w * 0.45,
  480. // );
  481. // },
  482. );
  483. }),
  484. );
  485. }),
  486. Spacer(),
  487. ],
  488. ),
  489. ));
  490. }
  491. Widget quickPhotoCard() {
  492. return Container(
  493. padding: EdgeInsets.symmetric(horizontal: 16.w),
  494. margin: EdgeInsets.only(top: 30.h),
  495. alignment: Alignment.centerLeft,
  496. child: Text(
  497. 'Quick Photo Clean',
  498. style: TextStyle(
  499. color: Colors.white,
  500. fontSize: 18.sp,
  501. fontWeight: FontWeight.w700,
  502. ),
  503. ),
  504. );
  505. }
  506. Widget peopleCard() {
  507. return GestureDetector(
  508. onTap: () {
  509. controller.peopleCleanClick();
  510. },
  511. child: Container(
  512. margin: EdgeInsets.only(top: 12.h),
  513. width: 328.w,
  514. height: 205.h,
  515. decoration: ShapeDecoration(
  516. color: Colors.white.withValues(alpha: 0.12),
  517. shape: RoundedRectangleBorder(
  518. borderRadius: BorderRadius.circular(14.sp),
  519. ),
  520. ),
  521. child: Stack(
  522. children: [
  523. Column(
  524. crossAxisAlignment: CrossAxisAlignment.start,
  525. children: [
  526. Spacer(),
  527. Padding(
  528. padding: EdgeInsets.only(left: 14.0.w),
  529. child: Text(
  530. 'People',
  531. style: TextStyle(
  532. color: Colors.white,
  533. fontSize: 16.sp,
  534. fontWeight: FontWeight.w700,
  535. ),
  536. ),
  537. ),
  538. Spacer(),
  539. Obx(() {
  540. return Row(
  541. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  542. children: List.generate(2, (index) {
  543. var image = Assets.images.iconHomeNoPhoto.image(
  544. width: 146.w * 0.45,
  545. height: 146.w * 0.45,
  546. );
  547. if (controller.peoplePhotos.length > index) {
  548. image = AssetEntityImage(
  549. width: 146.w,
  550. height: 146.w,
  551. controller.peoplePhotos[index],
  552. isOriginal: false,
  553. thumbnailSize: const ThumbnailSize.square(300),
  554. fit: BoxFit.cover,
  555. errorBuilder: (context, error, stackTrace) {
  556. return Assets.images.iconHomeNoPhoto.image(
  557. width: 146.w * 0.45,
  558. height: 146.w * 0.45,
  559. );
  560. });
  561. }
  562. return ImageContainer(
  563. image: Opacity(
  564. opacity: 0.22,
  565. child: Lottie.asset(Assets.anim.animNoPhoto,
  566. repeat: true, width: 140.w, height: 140.w),
  567. ),
  568. size: 146.w,
  569. // Image.file(
  570. // width: 146.w,
  571. // height: 146.w,
  572. // controller.peoplePhotos[index],
  573. // fit: BoxFit.cover,
  574. // ),
  575. // 可以传入不同的路径
  576. );
  577. }),
  578. );
  579. }),
  580. Spacer(),
  581. ],
  582. ),
  583. Positioned(
  584. bottom: 20.h,
  585. right: 20.w,
  586. child: Obx(() {
  587. return CleanUpButton(
  588. label: !controller.isScanned.value
  589. ? 'Scanning...'
  590. : 'Clean up',
  591. size: ImagePickerUtil.formatFileSize(
  592. ImagePickerUtil.peopleSize.value),
  593. onTap: () {
  594. controller.peopleCleanClick();
  595. },
  596. );
  597. }),
  598. ),
  599. ],
  600. ),
  601. ));
  602. }
  603. Widget locationsCard() {
  604. return GestureDetector(
  605. onTap: () {
  606. controller.locationCleanClick();
  607. },
  608. child: Container(
  609. padding: EdgeInsets.symmetric(horizontal: 12.w),
  610. margin: EdgeInsets.only(top: 14.h),
  611. width: 328.w,
  612. height: 230.h,
  613. decoration: ShapeDecoration(
  614. color: Colors.white.withValues(alpha: 0.12),
  615. shape: RoundedRectangleBorder(
  616. borderRadius: BorderRadius.circular(14.sp),
  617. ),
  618. ),
  619. child: Stack(
  620. children: [
  621. Column(
  622. crossAxisAlignment: CrossAxisAlignment.start,
  623. children: [
  624. Spacer(),
  625. Text(
  626. 'Locations',
  627. style: TextStyle(
  628. color: Colors.white,
  629. fontSize: 16.sp,
  630. fontWeight: FontWeight.w700,
  631. ),
  632. ),
  633. Spacer(),
  634. Container(
  635. width: 304.w,
  636. height: 171.h,
  637. clipBehavior: Clip.hardEdge,
  638. decoration: ShapeDecoration(
  639. color: Color(0xFF272C33),
  640. shape: RoundedRectangleBorder(
  641. borderRadius: BorderRadius.circular(12.r),
  642. ),
  643. ),
  644. child: Obx(() {
  645. return Center(
  646. child: controller.locationPhoto.value == null
  647. ? Opacity(
  648. opacity: 0.22,
  649. child: Lottie.asset(Assets.anim.animNoPhoto,
  650. repeat: true, width: 160.w, height: 160.w),
  651. )
  652. : AssetEntityImage(
  653. width: 304.w,
  654. height: 171.h,
  655. controller.locationPhoto.value!,
  656. isOriginal: false,
  657. thumbnailSize: const ThumbnailSize.square(300),
  658. fit: BoxFit.cover,
  659. errorBuilder: (context, error, stackTrace) {
  660. return Assets.images.iconHomeNoPhoto.image(
  661. width: 60.w,
  662. height: 60.h,
  663. );
  664. },
  665. ),
  666. // Image.file(
  667. // width: 304.w,
  668. // height: 171.h,
  669. // controller.locationPhoto.value!,
  670. // fit: BoxFit.cover,
  671. // ),
  672. );
  673. }),
  674. ),
  675. Spacer(),
  676. ],
  677. ),
  678. Positioned(
  679. bottom: 20.h,
  680. right: 8.w,
  681. child: Obx(() {
  682. return CleanUpButton(
  683. label: !controller.isScanned.value
  684. ? 'Scanning...'
  685. : 'Clean up',
  686. size: ImagePickerUtil.formatFileSize(
  687. ImagePickerUtil.locationsSize.value),
  688. onTap: () {
  689. controller.locationCleanClick();
  690. },
  691. );
  692. }),
  693. ),
  694. ],
  695. ),
  696. ));
  697. }
  698. Widget screenshotsAndBlurryCard() {
  699. return Container(
  700. padding: EdgeInsets.symmetric(horizontal: 16.w),
  701. margin: EdgeInsets.only(top: 14.h),
  702. child: Obx(() {
  703. return Row(
  704. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  705. children: [
  706. _buildCard(
  707. 'Screenshots',
  708. !controller.isScanned.value ? 'Scanning...' : 'Clean up',
  709. ImagePickerUtil.formatFileSize(
  710. ImagePickerUtil.screenshotsSize.value),
  711. controller.screenshotPhoto.value == null
  712. ? Opacity(
  713. opacity: 0.22,
  714. child: Lottie.asset(Assets.anim.animNoPhoto,
  715. repeat: true, width: 100.w, height: 100.w),
  716. )
  717. : AssetEntityImage(
  718. width: 144.w,
  719. height: 142.h,
  720. controller.screenshotPhoto.value!,
  721. isOriginal: false,
  722. thumbnailSize: const ThumbnailSize.square(300),
  723. fit: BoxFit.cover,
  724. errorBuilder: (context, error, stackTrace) {
  725. return Assets.images.iconHomeNoPhoto.image(
  726. width: 60.w,
  727. height: 60.h,
  728. );
  729. },
  730. ),
  731. onTap: () {
  732. controller.screenshotCleanClick();
  733. },
  734. ),
  735. _buildCard(
  736. 'Blurry',
  737. !controller.isScanned.value ? 'Scanning...' : 'Clean up',
  738. ImagePickerUtil.formatFileSize(
  739. ImagePickerUtil.blurrySize.value),
  740. controller.blurryPhoto.value == null
  741. ? Opacity(
  742. opacity: 0.22,
  743. child: Lottie.asset(Assets.anim.animNoPhoto,
  744. repeat: true, width: 100.w, height: 100.w),
  745. )
  746. : AssetEntityImage(
  747. width: 144.w,
  748. height: 142.h,
  749. controller.blurryPhoto.value!,
  750. isOriginal: false,
  751. thumbnailSize: const ThumbnailSize.square(300),
  752. fit: BoxFit.cover,
  753. errorBuilder: (context, error, stackTrace) {
  754. return Assets.images.iconHomeNoPhoto.image(
  755. width: 60.w,
  756. height: 60.h,
  757. );
  758. },
  759. ), onTap: () {
  760. controller.blurryCleanClick();
  761. }),
  762. ],
  763. );
  764. }),
  765. );
  766. }
  767. Widget _buildCard(String title, String buttonLabel, String size, Widget image,
  768. {required Function() onTap}) {
  769. return Stack(
  770. children: [
  771. GestureDetector(
  772. onTap: onTap,
  773. child: Container(
  774. width: 160.w,
  775. height: 189.h,
  776. decoration: ShapeDecoration(
  777. color: Colors.white.withValues(alpha: 0.12),
  778. shape: RoundedRectangleBorder(
  779. borderRadius: BorderRadius.circular(14.r),
  780. ),
  781. ),
  782. child: Column(
  783. crossAxisAlignment: CrossAxisAlignment.center,
  784. children: [
  785. Spacer(),
  786. Container(
  787. alignment: Alignment.centerLeft,
  788. padding: EdgeInsets.only(left: 9.w),
  789. child: Text(
  790. title,
  791. style: TextStyle(
  792. color: Colors.white,
  793. fontSize: 16.sp,
  794. fontWeight: FontWeight.w700,
  795. ),
  796. ),
  797. ),
  798. Spacer(),
  799. Container(
  800. width: 144.w,
  801. height: 142.h,
  802. clipBehavior: Clip.hardEdge,
  803. decoration: ShapeDecoration(
  804. color: Color(0xFF272C33),
  805. shape: RoundedRectangleBorder(
  806. borderRadius: BorderRadius.circular(12.r),
  807. ),
  808. ),
  809. child: Center(
  810. child: image,
  811. ),
  812. ),
  813. Spacer(),
  814. ],
  815. ),
  816. ),
  817. ),
  818. Positioned(
  819. bottom: 16.h,
  820. right: 16.w,
  821. child: CleanUpButton(
  822. label: buttonLabel,
  823. size: size,
  824. onTap: onTap,
  825. ),
  826. ),
  827. ],
  828. );
  829. }
  830. }
  831. class CleanUpButton extends StatelessWidget {
  832. final String label;
  833. final String size;
  834. final Function() onTap;
  835. const CleanUpButton({
  836. super.key,
  837. required this.label,
  838. required this.size,
  839. required this.onTap,
  840. });
  841. @override
  842. Widget build(BuildContext context) {
  843. return GestureDetector(
  844. onTap: onTap,
  845. child: Container(
  846. width: 94.w,
  847. height: 44.h,
  848. decoration: ShapeDecoration(
  849. color: Color(0xFF0279FB),
  850. shape: RoundedRectangleBorder(
  851. borderRadius: BorderRadius.circular(10.r),
  852. ),
  853. ),
  854. child: Row(
  855. mainAxisAlignment: MainAxisAlignment.spaceAround,
  856. children: [
  857. Column(
  858. crossAxisAlignment: CrossAxisAlignment.start,
  859. children: [
  860. Spacer(),
  861. Text(
  862. label,
  863. style: TextStyle(
  864. color: Colors.white,
  865. fontSize: 12.sp,
  866. fontWeight: FontWeight.w400,
  867. ),
  868. ),
  869. Text(
  870. size,
  871. style: TextStyle(
  872. color: Colors.white,
  873. fontSize: 16.sp,
  874. height: 1,
  875. fontWeight: FontWeight.w700,
  876. ),
  877. ),
  878. Spacer(),
  879. ],
  880. ),
  881. Assets.images.iconHomeRightArrow.image(
  882. width: 6.52.w,
  883. height: 6.52.h,
  884. ),
  885. ],
  886. ),
  887. ));
  888. }
  889. }
  890. class ImageContainer extends StatelessWidget {
  891. final Widget image;
  892. final double size;
  893. const ImageContainer({
  894. super.key,
  895. required this.image,
  896. required this.size,
  897. });
  898. @override
  899. Widget build(BuildContext context) {
  900. return Container(
  901. width: size,
  902. height: size,
  903. clipBehavior: Clip.hardEdge,
  904. decoration: BoxDecoration(
  905. color: Color(0xFF272C33),
  906. borderRadius: BorderRadius.circular(12.r),
  907. ),
  908. child: Center(
  909. child: image,
  910. ),
  911. );
  912. }
  913. }
  914. class PieData {
  915. final String label;
  916. final double value;
  917. final Color color;
  918. PieData(this.label, this.value, this.color);
  919. }