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/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. height: 234.h,
  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: () {
  69. Get.toNamed(RoutePath.discount);
  70. },
  71. child: Assets.images.iconHomeTitleVip
  72. .image(width: 60.8.w, height: 20.h),
  73. ),
  74. ],
  75. ),
  76. );
  77. }
  78. Widget storageCard() {
  79. return Container(
  80. margin: EdgeInsets.only(top: 20.h),
  81. padding: EdgeInsets.symmetric(horizontal: 16.w),
  82. width: 328.w,
  83. height: 146.h,
  84. decoration: ShapeDecoration(
  85. color: Colors.white.withValues(alpha: 0.10),
  86. shape: RoundedRectangleBorder(
  87. borderRadius: BorderRadius.circular(14.r),
  88. ),
  89. ),
  90. child: Row(
  91. children: [
  92. circularChartCard(),
  93. SizedBox(
  94. width: 14.w,
  95. ),
  96. storageInfoCard(),
  97. ],
  98. ));
  99. }
  100. Widget circularChartCard() {
  101. return SizedBox(
  102. width: 120.00.w,
  103. child: Obx(() {
  104. return SfCircularChart(
  105. series: <CircularSeries>[
  106. DoughnutSeries<PieData, String>(
  107. dataSource: [
  108. PieData('photo Space', controller.photoSpacePercentage,
  109. Colors.blue),
  110. PieData(
  111. 'Used Space',
  112. controller.usedSpacePercentage -
  113. controller.photoSpacePercentage,
  114. Colors.red),
  115. PieData(
  116. 'Unused Space',
  117. controller.freeSpacePercentage,
  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.usedSpacePercentage.toStringAsFixed(0),
  148. textAlign: TextAlign.end,
  149. style: TextStyle(
  150. color: Colors.white
  151. .withValues(alpha: 0.8999999761581421),
  152. fontSize: 30.sp,
  153. height: 1,
  154. fontWeight: FontWeight.w400,
  155. ),
  156. );
  157. }),
  158. Text(
  159. '%',
  160. textAlign: TextAlign.end,
  161. style: TextStyle(
  162. color: Colors.white
  163. .withValues(alpha: 0.8999999761581421),
  164. fontSize: 14.87.r,
  165. fontWeight: FontWeight.w500,
  166. ),
  167. ),
  168. ],
  169. ),
  170. Text(
  171. 'used',
  172. textAlign: TextAlign.center,
  173. style: TextStyle(
  174. color: Colors.white.withValues(
  175. alpha: 0.6000000238418579),
  176. fontSize: 14.87.r,
  177. height: 1,
  178. fontWeight: FontWeight.w500,
  179. ),
  180. )
  181. ],
  182. )),
  183. horizontalAlignment: ChartAlignment.center,
  184. verticalAlignment: ChartAlignment.center,
  185. radius: '0%',
  186. ),
  187. ],
  188. );
  189. }),
  190. );
  191. }
  192. Widget storageInfoCard() {
  193. return Column(
  194. crossAxisAlignment: CrossAxisAlignment.start,
  195. children: [
  196. SizedBox(
  197. height: 24.h,
  198. ),
  199. Text(
  200. 'Storage Used',
  201. style: TextStyle(
  202. color: Colors.white,
  203. fontSize: 16.sp,
  204. fontWeight: FontWeight.w500,
  205. ),
  206. ),
  207. Row(
  208. children: [
  209. Obx(() {
  210. return Text.rich(
  211. TextSpan(
  212. children: [
  213. TextSpan(
  214. text: ImagePickerUtil.formatSize(
  215. controller.usedSpace.value),
  216. style: TextStyle(
  217. color: Color(0xFFFC4C4F),
  218. fontSize: 13.sp,
  219. fontWeight: FontWeight.w400,
  220. ),
  221. ),
  222. TextSpan(
  223. text: 'GB',
  224. style: TextStyle(
  225. color: Color(0xFFFC4C4F),
  226. fontSize: 13.sp,
  227. fontWeight: FontWeight.w500,
  228. ),
  229. ),
  230. ],
  231. ),
  232. textAlign: TextAlign.center,
  233. );
  234. }),
  235. Text(
  236. ' / ',
  237. style: TextStyle(
  238. color: Colors.white.withValues(alpha: 0.6000000238418579),
  239. fontSize: 13.sp,
  240. fontWeight: FontWeight.w500,
  241. ),
  242. ),
  243. Obx(() {
  244. return Text.rich(
  245. TextSpan(
  246. children: [
  247. TextSpan(
  248. text: ImagePickerUtil.formatSize(
  249. controller.totalSpace.value),
  250. style: TextStyle(
  251. color:
  252. Colors.white.withValues(alpha: 0.6000000238418579),
  253. fontSize: 13.sp,
  254. fontWeight: FontWeight.w400,
  255. ),
  256. ),
  257. TextSpan(
  258. text: 'GB',
  259. style: TextStyle(
  260. color:
  261. Colors.white.withValues(alpha: 0.6000000238418579),
  262. fontSize: 13.sp,
  263. fontWeight: FontWeight.w500,
  264. ),
  265. ),
  266. ],
  267. ),
  268. textAlign: TextAlign.center,
  269. );
  270. }),
  271. ],
  272. ),
  273. SizedBox(
  274. height: 10.h,
  275. ),
  276. Row(
  277. crossAxisAlignment: CrossAxisAlignment.start,
  278. children: [
  279. Container(
  280. margin: EdgeInsets.only(top: 7.h),
  281. width: 6.w,
  282. height: 6.h,
  283. decoration: ShapeDecoration(
  284. color: Color(0xFF0279FB),
  285. shape: OvalBorder(),
  286. ),
  287. ),
  288. SizedBox(
  289. width: 3.5.w,
  290. ),
  291. Column(
  292. crossAxisAlignment: CrossAxisAlignment.start,
  293. children: [
  294. Text(
  295. 'Photos',
  296. textAlign: TextAlign.start,
  297. style: TextStyle(
  298. color: Colors.white,
  299. fontSize: 14.sp,
  300. fontWeight: FontWeight.w500,
  301. ),
  302. ),
  303. Obx(() {
  304. return Text(
  305. '${controller.photoSpaceStr}',
  306. textAlign: TextAlign.start,
  307. style: TextStyle(
  308. color: Colors.white.withValues(alpha: 0.6499999761581421),
  309. fontSize: 13.sp,
  310. fontWeight: FontWeight.w400,
  311. ),
  312. );
  313. }),
  314. ],
  315. ),
  316. SizedBox(
  317. width: 10.w,
  318. ),
  319. Container(
  320. margin: EdgeInsets.only(top: 7.h),
  321. width: 6.w,
  322. height: 6.h,
  323. decoration: ShapeDecoration(
  324. color: Color(0xFFEE4933),
  325. shape: OvalBorder(),
  326. ),
  327. ),
  328. SizedBox(
  329. width: 3.5.w,
  330. ),
  331. Column(
  332. crossAxisAlignment: CrossAxisAlignment.start,
  333. children: [
  334. Text(
  335. 'iphone',
  336. textAlign: TextAlign.start,
  337. style: TextStyle(
  338. color: Colors.white,
  339. fontSize: 14.sp,
  340. fontWeight: FontWeight.w500,
  341. ),
  342. ),
  343. Obx(() {
  344. return Text(
  345. '${controller.usedSpace.toStringAsFixed(1)} GB',
  346. textAlign: TextAlign.start,
  347. style: TextStyle(
  348. color: Colors.white.withValues(alpha: 0.6499999761581421),
  349. fontSize: 13.sp,
  350. fontWeight: FontWeight.w400,
  351. ),
  352. );
  353. }),
  354. ],
  355. ),
  356. ],
  357. ),
  358. ],
  359. );
  360. }
  361. Widget similarCard() {
  362. return Container(
  363. width: 328.w,
  364. height: 155.h,
  365. margin: EdgeInsets.only(top: 20.h),
  366. padding: EdgeInsets.symmetric(horizontal: 16.w),
  367. decoration: ShapeDecoration(
  368. color: Colors.white.withValues(alpha: 0.12),
  369. shape: RoundedRectangleBorder(
  370. borderRadius: BorderRadius.circular(16.r),
  371. ),
  372. ),
  373. child: Column(
  374. crossAxisAlignment: CrossAxisAlignment.start,
  375. children: [
  376. SizedBox(height: 12.h),
  377. Row(
  378. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  379. children: [
  380. Column(
  381. crossAxisAlignment: CrossAxisAlignment.start,
  382. children: [
  383. Text(
  384. 'Similar',
  385. style: TextStyle(
  386. color: Colors.white,
  387. fontSize: 16.sp,
  388. fontWeight: FontWeight.w700,
  389. ),
  390. ),
  391. Obx(() {
  392. return Text.rich(
  393. TextSpan(
  394. children: [
  395. TextSpan(
  396. text: "${ImagePickerUtil.similarPhotoCount.value}",
  397. style: TextStyle(
  398. color: Colors.white,
  399. fontSize: 12.sp,
  400. fontWeight: FontWeight.w400,
  401. ),
  402. ),
  403. TextSpan(
  404. text: ' duplicate photos detected',
  405. style: TextStyle(
  406. color: Colors.white
  407. .withValues(alpha: 0.800000011920929),
  408. fontSize: 12.sp,
  409. fontWeight: FontWeight.w400,
  410. ),
  411. ),
  412. ],
  413. ),
  414. );
  415. }),
  416. ],
  417. ),
  418. Obx(() {
  419. return CleanUpButton(
  420. label:
  421. !controller.isScanned.value ? 'Scanning...' : 'Clean up',
  422. size: ImagePickerUtil.formatFileSize(
  423. ImagePickerUtil.similarPhotosSize.value),
  424. onTap: () {
  425. controller.similarCleanClick();
  426. },
  427. );
  428. }),
  429. ],
  430. ),
  431. // SizedBox(height: 19.h),
  432. Spacer(),
  433. Obx(() {
  434. return Row(
  435. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  436. children: List.generate(4, (index) {
  437. var image = Assets.images.iconHomeNoPhoto.image(
  438. width: 70.w * 0.45,
  439. height: 70.w * 0.45,
  440. );
  441. if (controller.similarPhotos.length > index) {
  442. image = AssetEntityImage(
  443. width: 70.w,
  444. height: 70.w,
  445. controller.similarPhotos[index],
  446. isOriginal: false,
  447. thumbnailSize: const ThumbnailSize.square(300),
  448. fit: BoxFit.cover,
  449. errorBuilder: (context, error, stackTrace) {
  450. return Assets.images.iconHomeNoPhoto.image(
  451. width: 70.w * 0.45,
  452. height: 70.w * 0.45,
  453. );
  454. });
  455. }
  456. return ImageContainer(
  457. size: 70.w,
  458. image: image,
  459. // AssetEntityImage(
  460. // width: 70.w,
  461. // height: 70.w,
  462. // controller.similarPhotos[index],
  463. // isOriginal: false,
  464. // thumbnailSize: const ThumbnailSize.square(300),
  465. // fit: BoxFit.cover,
  466. // errorBuilder: (context, error, stackTrace) {
  467. // return Assets.images.iconHomeNoPhoto.image(
  468. // width: 70.w * 0.45,
  469. // height: 70.w * 0.45,
  470. // );
  471. // },
  472. );
  473. }),
  474. );
  475. }),
  476. Spacer(),
  477. ],
  478. ),
  479. );
  480. }
  481. Widget quickPhotoCard() {
  482. return Container(
  483. padding: EdgeInsets.symmetric(horizontal: 16.w),
  484. margin: EdgeInsets.only(top: 30.h),
  485. alignment: Alignment.centerLeft,
  486. child: Text(
  487. 'Quick Photo Clean',
  488. style: TextStyle(
  489. color: Colors.white,
  490. fontSize: 18.sp,
  491. fontWeight: FontWeight.w700,
  492. ),
  493. ),
  494. );
  495. }
  496. Widget peopleCard() {
  497. return Container(
  498. margin: EdgeInsets.only(top: 12.h),
  499. width: 328.w,
  500. height: 205.h,
  501. decoration: ShapeDecoration(
  502. color: Colors.white.withValues(alpha: 0.12),
  503. shape: RoundedRectangleBorder(
  504. borderRadius: BorderRadius.circular(14.sp),
  505. ),
  506. ),
  507. child: Stack(
  508. children: [
  509. Column(
  510. crossAxisAlignment: CrossAxisAlignment.start,
  511. children: [
  512. Spacer(),
  513. Padding(
  514. padding: EdgeInsets.only(left: 14.0.w),
  515. child: Text(
  516. 'People',
  517. style: TextStyle(
  518. color: Colors.white,
  519. fontSize: 16.sp,
  520. fontWeight: FontWeight.w700,
  521. ),
  522. ),
  523. ),
  524. Spacer(),
  525. Obx(() {
  526. return Row(
  527. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  528. children: List.generate(2, (index) {
  529. var image = Assets.images.iconHomeNoPhoto.image(
  530. width: 146.w * 0.45,
  531. height: 146.w * 0.45,
  532. );
  533. if (controller.peoplePhotos.length > index) {
  534. image = AssetEntityImage(
  535. width: 146.w,
  536. height: 146.w,
  537. controller.peoplePhotos[index],
  538. isOriginal: false,
  539. thumbnailSize: const ThumbnailSize.square(300),
  540. fit: BoxFit.cover,
  541. errorBuilder: (context, error, stackTrace) {
  542. return Assets.images.iconHomeNoPhoto.image(
  543. width: 146.w * 0.45,
  544. height: 146.w * 0.45,
  545. );
  546. });
  547. }
  548. return ImageContainer(
  549. image: image,
  550. size: 146.w,
  551. // Image.file(
  552. // width: 146.w,
  553. // height: 146.w,
  554. // controller.peoplePhotos[index],
  555. // fit: BoxFit.cover,
  556. // ),
  557. // 可以传入不同的路径
  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. }