home_view.dart 28 KB

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