home_view.dart 29 KB

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