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