home_view.dart 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  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: const CircularProgressIndicator(color: Colors.white38,)
  467. ),
  468. // AssetEntityImage(
  469. // width: 70.w,
  470. // height: 70.w,
  471. // controller.similarPhotos[index],
  472. // isOriginal: false,
  473. // thumbnailSize: const ThumbnailSize.square(300),
  474. // fit: BoxFit.cover,
  475. // errorBuilder: (context, error, stackTrace) {
  476. // return Assets.images.iconHomeNoPhoto.image(
  477. // width: 70.w * 0.45,
  478. // height: 70.w * 0.45,
  479. // );
  480. // },
  481. );
  482. }),
  483. );
  484. }),
  485. Spacer(),
  486. ],
  487. ),
  488. ));
  489. }
  490. Widget quickPhotoCard() {
  491. return Container(
  492. padding: EdgeInsets.symmetric(horizontal: 16.w),
  493. margin: EdgeInsets.only(top: 30.h),
  494. alignment: Alignment.centerLeft,
  495. child: Text(
  496. 'Quick Photo Clean',
  497. style: TextStyle(
  498. color: Colors.white,
  499. fontSize: 18.sp,
  500. fontWeight: FontWeight.w700,
  501. ),
  502. ),
  503. );
  504. }
  505. Widget peopleCard() {
  506. return GestureDetector(
  507. onTap: () {
  508. controller.peopleCleanClick();
  509. },
  510. child: Container(
  511. margin: EdgeInsets.only(top: 12.h),
  512. width: 328.w,
  513. height: 205.h,
  514. decoration: ShapeDecoration(
  515. color: Colors.white.withValues(alpha: 0.12),
  516. shape: RoundedRectangleBorder(
  517. borderRadius: BorderRadius.circular(14.sp),
  518. ),
  519. ),
  520. child: Stack(
  521. children: [
  522. Column(
  523. crossAxisAlignment: CrossAxisAlignment.start,
  524. children: [
  525. Spacer(),
  526. Padding(
  527. padding: EdgeInsets.only(left: 14.0.w),
  528. child: Text(
  529. 'People',
  530. style: TextStyle(
  531. color: Colors.white,
  532. fontSize: 16.sp,
  533. fontWeight: FontWeight.w700,
  534. ),
  535. ),
  536. ),
  537. Spacer(),
  538. Obx(() {
  539. return Row(
  540. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  541. children: List.generate(2, (index) {
  542. var image = Assets.images.iconHomeNoPhoto.image(
  543. width: 146.w * 0.45,
  544. height: 146.w * 0.45,
  545. );
  546. if (controller.peoplePhotos.length > index) {
  547. image = AssetEntityImage(
  548. width: 146.w,
  549. height: 146.w,
  550. controller.peoplePhotos[index],
  551. isOriginal: false,
  552. thumbnailSize: const ThumbnailSize.square(300),
  553. fit: BoxFit.cover,
  554. errorBuilder: (context, error, stackTrace) {
  555. return Assets.images.iconHomeNoPhoto.image(
  556. width: 146.w * 0.45,
  557. height: 146.w * 0.45,
  558. );
  559. });
  560. }
  561. return ImageContainer(
  562. image: Opacity(
  563. opacity: 0.22,
  564. child: const CircularProgressIndicator(color: Colors.white38,),
  565. ),
  566. size: 146.w,
  567. // Image.file(
  568. // width: 146.w,
  569. // height: 146.w,
  570. // controller.peoplePhotos[index],
  571. // fit: BoxFit.cover,
  572. // ),
  573. // 可以传入不同的路径
  574. );
  575. }),
  576. );
  577. }),
  578. Spacer(),
  579. ],
  580. ),
  581. Positioned(
  582. bottom: 20.h,
  583. right: 20.w,
  584. child: Obx(() {
  585. return CleanUpButton(
  586. label: !controller.isScanned.value
  587. ? 'Scanning...'
  588. : 'Clean up',
  589. size: ImagePickerUtil.formatFileSize(
  590. ImagePickerUtil.peopleSize.value),
  591. onTap: () {
  592. controller.peopleCleanClick();
  593. },
  594. );
  595. }),
  596. ),
  597. ],
  598. ),
  599. ));
  600. }
  601. Widget locationsCard() {
  602. return GestureDetector(
  603. onTap: () {
  604. controller.locationCleanClick();
  605. },
  606. child: Container(
  607. padding: EdgeInsets.symmetric(horizontal: 12.w),
  608. margin: EdgeInsets.only(top: 14.h),
  609. width: 328.w,
  610. height: 230.h,
  611. decoration: ShapeDecoration(
  612. color: Colors.white.withValues(alpha: 0.12),
  613. shape: RoundedRectangleBorder(
  614. borderRadius: BorderRadius.circular(14.sp),
  615. ),
  616. ),
  617. child: Stack(
  618. children: [
  619. Column(
  620. crossAxisAlignment: CrossAxisAlignment.start,
  621. children: [
  622. Spacer(),
  623. Text(
  624. 'Locations',
  625. style: TextStyle(
  626. color: Colors.white,
  627. fontSize: 16.sp,
  628. fontWeight: FontWeight.w700,
  629. ),
  630. ),
  631. Spacer(),
  632. Container(
  633. width: 304.w,
  634. height: 171.h,
  635. clipBehavior: Clip.hardEdge,
  636. decoration: ShapeDecoration(
  637. color: Color(0xFF272C33),
  638. shape: RoundedRectangleBorder(
  639. borderRadius: BorderRadius.circular(12.r),
  640. ),
  641. ),
  642. child: Obx(() {
  643. return Center(
  644. child: controller.locationPhoto.value == null
  645. ? Opacity(
  646. opacity: 0.22,
  647. child: const CircularProgressIndicator(color: Colors.white38,),
  648. )
  649. : AssetEntityImage(
  650. width: 304.w,
  651. height: 171.h,
  652. controller.locationPhoto.value!,
  653. isOriginal: false,
  654. thumbnailSize: const ThumbnailSize.square(300),
  655. fit: BoxFit.cover,
  656. errorBuilder: (context, error, stackTrace) {
  657. return Assets.images.iconHomeNoPhoto.image(
  658. width: 60.w,
  659. height: 60.h,
  660. );
  661. },
  662. ),
  663. // Image.file(
  664. // width: 304.w,
  665. // height: 171.h,
  666. // controller.locationPhoto.value!,
  667. // fit: BoxFit.cover,
  668. // ),
  669. );
  670. }),
  671. ),
  672. Spacer(),
  673. ],
  674. ),
  675. Positioned(
  676. bottom: 20.h,
  677. right: 8.w,
  678. child: Obx(() {
  679. return CleanUpButton(
  680. label: !controller.isScanned.value
  681. ? 'Scanning...'
  682. : 'Clean up',
  683. size: ImagePickerUtil.formatFileSize(
  684. ImagePickerUtil.locationsSize.value),
  685. onTap: () {
  686. controller.locationCleanClick();
  687. },
  688. );
  689. }),
  690. ),
  691. ],
  692. ),
  693. ));
  694. }
  695. Widget screenshotsAndBlurryCard() {
  696. return Container(
  697. padding: EdgeInsets.symmetric(horizontal: 16.w),
  698. margin: EdgeInsets.only(top: 14.h),
  699. child: Obx(() {
  700. return Row(
  701. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  702. children: [
  703. _buildCard(
  704. 'Screenshots',
  705. !controller.isScanned.value ? 'Scanning...' : 'Clean up',
  706. ImagePickerUtil.formatFileSize(
  707. ImagePickerUtil.screenshotsSize.value),
  708. controller.screenshotPhoto.value == null
  709. ? Opacity(
  710. opacity: 0.22,
  711. child: const CircularProgressIndicator(color: Colors.white38,),
  712. )
  713. : AssetEntityImage(
  714. width: 144.w,
  715. height: 142.h,
  716. controller.screenshotPhoto.value!,
  717. isOriginal: false,
  718. thumbnailSize: const ThumbnailSize.square(300),
  719. fit: BoxFit.cover,
  720. errorBuilder: (context, error, stackTrace) {
  721. return Assets.images.iconHomeNoPhoto.image(
  722. width: 60.w,
  723. height: 60.h,
  724. );
  725. },
  726. ),
  727. onTap: () {
  728. controller.screenshotCleanClick();
  729. },
  730. ),
  731. _buildCard(
  732. 'Blurry',
  733. !controller.isScanned.value ? 'Scanning...' : 'Clean up',
  734. ImagePickerUtil.formatFileSize(
  735. ImagePickerUtil.blurrySize.value),
  736. controller.blurryPhoto.value == null
  737. ? Opacity(
  738. opacity: 0.22,
  739. child: const CircularProgressIndicator(color: Colors.white38,),
  740. )
  741. : AssetEntityImage(
  742. width: 144.w,
  743. height: 142.h,
  744. controller.blurryPhoto.value!,
  745. isOriginal: false,
  746. thumbnailSize: const ThumbnailSize.square(300),
  747. fit: BoxFit.cover,
  748. errorBuilder: (context, error, stackTrace) {
  749. return Assets.images.iconHomeNoPhoto.image(
  750. width: 60.w,
  751. height: 60.h,
  752. );
  753. },
  754. ), onTap: () {
  755. controller.blurryCleanClick();
  756. }),
  757. ],
  758. );
  759. }),
  760. );
  761. }
  762. Widget _buildCard(String title, String buttonLabel, String size, Widget image,
  763. {required Function() onTap}) {
  764. return Stack(
  765. children: [
  766. GestureDetector(
  767. onTap: onTap,
  768. child: Container(
  769. width: 160.w,
  770. height: 189.h,
  771. decoration: ShapeDecoration(
  772. color: Colors.white.withValues(alpha: 0.12),
  773. shape: RoundedRectangleBorder(
  774. borderRadius: BorderRadius.circular(14.r),
  775. ),
  776. ),
  777. child: Column(
  778. crossAxisAlignment: CrossAxisAlignment.center,
  779. children: [
  780. Spacer(),
  781. Container(
  782. alignment: Alignment.centerLeft,
  783. padding: EdgeInsets.only(left: 9.w),
  784. child: Text(
  785. title,
  786. style: TextStyle(
  787. color: Colors.white,
  788. fontSize: 16.sp,
  789. fontWeight: FontWeight.w700,
  790. ),
  791. ),
  792. ),
  793. Spacer(),
  794. Container(
  795. width: 144.w,
  796. height: 142.h,
  797. clipBehavior: Clip.hardEdge,
  798. decoration: ShapeDecoration(
  799. color: Color(0xFF272C33),
  800. shape: RoundedRectangleBorder(
  801. borderRadius: BorderRadius.circular(12.r),
  802. ),
  803. ),
  804. child: Center(
  805. child: image,
  806. ),
  807. ),
  808. Spacer(),
  809. ],
  810. ),
  811. ),
  812. ),
  813. Positioned(
  814. bottom: 16.h,
  815. right: 16.w,
  816. child: CleanUpButton(
  817. label: buttonLabel,
  818. size: size,
  819. onTap: onTap,
  820. ),
  821. ),
  822. ],
  823. );
  824. }
  825. }
  826. class CleanUpButton extends StatelessWidget {
  827. final String label;
  828. final String size;
  829. final Function() onTap;
  830. const CleanUpButton({
  831. super.key,
  832. required this.label,
  833. required this.size,
  834. required this.onTap,
  835. });
  836. @override
  837. Widget build(BuildContext context) {
  838. return GestureDetector(
  839. onTap: onTap,
  840. child: Container(
  841. width: 94.w,
  842. height: 44.h,
  843. decoration: ShapeDecoration(
  844. color: Color(0xFF0279FB),
  845. shape: RoundedRectangleBorder(
  846. borderRadius: BorderRadius.circular(10.r),
  847. ),
  848. ),
  849. child: Row(
  850. mainAxisAlignment: MainAxisAlignment.spaceAround,
  851. children: [
  852. Column(
  853. crossAxisAlignment: CrossAxisAlignment.start,
  854. children: [
  855. Spacer(),
  856. Text(
  857. label,
  858. style: TextStyle(
  859. color: Colors.white,
  860. fontSize: 12.sp,
  861. fontWeight: FontWeight.w400,
  862. ),
  863. ),
  864. Text(
  865. size,
  866. style: TextStyle(
  867. color: Colors.white,
  868. fontSize: 16.sp,
  869. height: 1,
  870. fontWeight: FontWeight.w700,
  871. ),
  872. ),
  873. Spacer(),
  874. ],
  875. ),
  876. Assets.images.iconHomeRightArrow.image(
  877. width: 6.52.w,
  878. height: 6.52.h,
  879. ),
  880. ],
  881. ),
  882. ));
  883. }
  884. }
  885. class ImageContainer extends StatelessWidget {
  886. final Widget image;
  887. final double size;
  888. const ImageContainer({
  889. super.key,
  890. required this.image,
  891. required this.size,
  892. });
  893. @override
  894. Widget build(BuildContext context) {
  895. return Container(
  896. width: size,
  897. height: size,
  898. clipBehavior: Clip.hardEdge,
  899. decoration: BoxDecoration(
  900. color: Color(0xFF272C33),
  901. borderRadius: BorderRadius.circular(12.r),
  902. ),
  903. child: Center(
  904. child: image,
  905. ),
  906. );
  907. }
  908. }
  909. class PieData {
  910. final String label;
  911. final double value;
  912. final Color color;
  913. PieData(this.label, this.value, this.color);
  914. }