consume_page.dart 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart' as extended;
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
  4. import 'package:sport/bean/share_info.dart';
  5. import 'package:sport/bean/sport_detail.dart';
  6. import 'package:sport/bean/sport_index.dart';
  7. import 'package:sport/pages/home/step_page.dart';
  8. import 'package:sport/pages/social/share_webview.dart';
  9. import 'package:sport/router/navigator_util.dart';
  10. import 'package:sport/services/api/inject_api.dart';
  11. import 'package:sport/services/api/resp.dart';
  12. import 'package:sport/utils/date.dart';
  13. import 'package:sport/widgets/appbar.dart';
  14. import 'package:sport/widgets/chart.dart';
  15. import 'package:sport/widgets/decoration.dart';
  16. import 'package:sport/widgets/dialog/request_dialog.dart';
  17. import 'package:sport/widgets/image.dart';
  18. import 'package:sport/widgets/loading.dart';
  19. import 'package:sport/widgets/misc.dart';
  20. import 'package:sport/widgets/persistent_header.dart';
  21. class ConsumePage extends StatefulWidget {
  22. @override
  23. State<StatefulWidget> createState() => _PageState();
  24. }
  25. class _PageState extends State<ConsumePage> with InjectApi {
  26. Color _color = Color(0xffFFC400);
  27. ValueNotifier<String> _tab = ValueNotifier<String>("日");
  28. ValueNotifier<SportDetail> _valueNotifierSportDetail = ValueNotifier(null);
  29. ValueNotifier<DateTime> _valueNotifierDate = ValueNotifier(DateTime.now());
  30. ValueNotifier<DateTime> _valueNotifierNow = ValueNotifier(DateTime.now());
  31. PageController _pageController;
  32. @override
  33. void initState() {
  34. super.initState();
  35. _pageController = PageController(initialPage: 0);
  36. changeTab();
  37. }
  38. @override
  39. void dispose() {
  40. _pageController?.dispose();
  41. super.dispose();
  42. _tab?.dispose();
  43. _valueNotifierSportDetail?.dispose();
  44. _valueNotifierDate?.dispose();
  45. _valueNotifierNow?.dispose();
  46. }
  47. @override
  48. Widget build(BuildContext context) {
  49. final double tabHeader = 100.0;
  50. final double statusBarHeight = MediaQuery.of(context).padding.top;
  51. final double pinnedHeaderHeight = tabHeader + statusBarHeight;
  52. return Scaffold(
  53. backgroundColor: Colors.white,
  54. body: Stack(
  55. children: <Widget>[
  56. extended.NestedScrollView(
  57. pinnedHeaderSliverHeightBuilder: () {
  58. return pinnedHeaderHeight;
  59. },
  60. innerScrollPositionKeyBuilder: () {
  61. PageController controller = _pageController;
  62. String index = 'Tab${controller.page}';
  63. return Key(index);
  64. },
  65. headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
  66. return <Widget>[
  67. SliverToBoxAdapter(
  68. child: Container(
  69. width: 240.0,
  70. height: 240.0,
  71. child: Align(
  72. alignment: Alignment.bottomCenter,
  73. child: CustomPaint(
  74. painter: _Bg(),
  75. child: Container(
  76. width: 180.0,
  77. height: 180.0,
  78. child: Center(
  79. child: Column(
  80. children: <Widget>[
  81. Text("消耗卡路里", style: Theme.of(context).textTheme.subtitle1),
  82. SizedBox(
  83. height: 22.0,
  84. ),
  85. Row(
  86. children: <Widget>[
  87. ValueListenableBuilder(
  88. builder: (BuildContext context, value, Widget child) => FutureBuilder(
  89. future: createFutureType(0, _valueNotifierNow.value),
  90. builder: (BuildContext context, AsyncSnapshot<SportDetail> snapshot) => Text(
  91. "${snapshot?.data?.recordsTodaySum?.consume ?? 0}",
  92. style: Theme.of(context).textTheme.headline1.copyWith(fontSize: 40.0),
  93. strutStyle: fixedLine,
  94. ),
  95. ),
  96. valueListenable: _valueNotifierNow,
  97. ),
  98. Text(" 卡", style: Theme.of(context).textTheme.subtitle2),
  99. ],
  100. mainAxisSize: MainAxisSize.min,
  101. crossAxisAlignment: CrossAxisAlignment.end,
  102. ),
  103. SizedBox(
  104. height: 10,
  105. ),
  106. GestureDetector(
  107. onTap: () async {
  108. var result = await showDatePicker(
  109. context: context,
  110. initialDate: _valueNotifierNow.value,
  111. lastDate: DateTime.now(),
  112. firstDate: DateTime(2020),
  113. );
  114. if (result != null) {
  115. var diff = DateTime.now().difference(result);
  116. _valueNotifierDate.value = result;
  117. _valueNotifierNow.value = result;
  118. int type = toType();
  119. // if (type == 0) {
  120. // _pageController.jumpToPage(diff.inDays);
  121. // } else {
  122. // _pageController = PageController(initialPage: diff.inDays);
  123. // }
  124. _tab.value = TABS.first;
  125. _pageController.jumpToPage(diff.inDays);
  126. print("$type -- ${diff.inDays}");
  127. }
  128. },
  129. child: Row(
  130. children: <Widget>[
  131. ValueListenableBuilder(
  132. valueListenable: _valueNotifierNow,
  133. builder: (BuildContext context, DateTime value, Widget child) =>
  134. Text("${value.month}.${value.day}", style: Theme.of(context).textTheme.subtitle1),
  135. ),
  136. SizedBox(
  137. width: 12.0,
  138. ),
  139. Image.asset("lib/assets/img/setgoals_icon_date.png"),
  140. ],
  141. mainAxisSize: MainAxisSize.min,
  142. ),
  143. behavior: HitTestBehavior.opaque,
  144. )
  145. ],
  146. mainAxisSize: MainAxisSize.min,
  147. ),
  148. ),
  149. ),
  150. ),
  151. ),
  152. ),
  153. ),
  154. SliverPersistentHeader(
  155. pinned: true,
  156. delegate: PersistentHeader(
  157. min: pinnedHeaderHeight,
  158. max: pinnedHeaderHeight,
  159. child: Container(
  160. color: Colors.white,
  161. child: ValueListenableBuilder(
  162. valueListenable: _tab,
  163. builder: (BuildContext context, String value, Widget child) {
  164. return Column(
  165. children: <Widget>[
  166. SafeArea(
  167. child: Padding(
  168. padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 5.0),
  169. child: Row(
  170. mainAxisAlignment: MainAxisAlignment.center,
  171. children: ["日", "/", "周", "/", "月", "/", "年"]
  172. .map((e) => e == "/"
  173. ? Container(
  174. margin: const EdgeInsets.fromLTRB(5, 0, 1, 0),
  175. color: const Color(0xffdcdcdc),
  176. width: 0.5,
  177. height: 14,
  178. transform: Matrix4.rotationZ(0.35),
  179. )
  180. : InkWell(
  181. onTap: () {
  182. if (_valueNotifierSportDetail.value == null) return;
  183. _tab.value = e;
  184. _valueNotifierDate.value = DateTime.now();
  185. _pageController.jumpToPage(0);
  186. // _pageController= PageController(initialPage: 0);
  187. changeTab();
  188. },
  189. child: Container(
  190. margin: EdgeInsets.symmetric(horizontal: 12.0),
  191. padding: EdgeInsets.all(8.0),
  192. decoration: value == e ? BoxDecoration(color: _color, shape: BoxShape.circle) : null,
  193. child: Text(
  194. "$e",
  195. style: value == e
  196. ? Theme.of(context).textTheme.subtitle1.copyWith(color: Colors.white)
  197. : Theme.of(context).textTheme.subtitle1,
  198. ),
  199. )))
  200. .toList(),
  201. ),
  202. ),
  203. ),
  204. Center(
  205. child: ValueListenableBuilder<DateTime>(
  206. valueListenable: _valueNotifierDate,
  207. builder: (_, time, ___) {
  208. int type = toType();
  209. String text = "";
  210. if (type == 0) {
  211. text = "${time.year}.${'${time.month}'.padLeft(2, '0')}.${'${time.day}'.padLeft(2, '0')} 6:00 - 24:00 ";
  212. } else if (type == 1) {
  213. DateTime start = DateTime(time.year, time.month, time.day - time.weekday + 1);
  214. DateTime end = DateTime(time.year, time.month, time.day + 6 - time.weekday + 1);
  215. print("$time ${time.weekday} == $start $end");
  216. text =
  217. "${start.year}.${'${start.month}'.padLeft(2, '0')}.${'${start.day}'.padLeft(2, '0')} ~ ${end.year}.${'${end.month}'.padLeft(2, '0')}.${'${end.day}'.padLeft(2, '0')}";
  218. } else if (type == 2) {
  219. text = ("${time.year}年${'${time.month}'.padLeft(2, '0')}月");
  220. } else if (type == 3) {
  221. text = ("${time.year}年");
  222. }
  223. return Row(
  224. mainAxisSize: MainAxisSize.min,
  225. children: <Widget>[
  226. GestureDetector(
  227. behavior: HitTestBehavior.opaque,
  228. onTap: () {
  229. _pageController?.nextPage(duration: Duration(milliseconds: 500), curve: Curves.linear);
  230. },
  231. child: Padding(
  232. padding: const EdgeInsets.all(18.0),
  233. child: arrowLeft(),
  234. ),
  235. ),
  236. Text(
  237. text,
  238. style: Theme.of(context).textTheme.bodyText2,
  239. strutStyle: fixedLine,
  240. ),
  241. GestureDetector(
  242. behavior: HitTestBehavior.opaque,
  243. onTap: () {
  244. if (_pageController?.page == 0.0) {
  245. return;
  246. }
  247. _pageController?.previousPage(duration: Duration(milliseconds: 500), curve: Curves.linear);
  248. },
  249. child: Padding(
  250. padding: const EdgeInsets.all(18.0),
  251. child: arrowRight(),
  252. ),
  253. ),
  254. ],
  255. );
  256. }),
  257. ),
  258. ],
  259. );
  260. },
  261. ),
  262. ),
  263. ),
  264. ),
  265. ];
  266. },
  267. body: ValueListenableBuilder(
  268. valueListenable: _tab,
  269. builder: (BuildContext context, String value, Widget child) => PageView.builder(
  270. reverse: true,
  271. itemCount: 10240,
  272. controller: _pageController,
  273. onPageChanged: (page) {
  274. rollDate(-page);
  275. },
  276. itemBuilder: (context, index) {
  277. int type = toType();
  278. DateTime time = offsetDate(type, -index);
  279. print("$index $type --2222 ${time}");
  280. return extended.NestedScrollViewInnerScrollPositionKeyWidget(
  281. Key('Tab$index'),
  282. FutureBuilder<SportDetail>(
  283. future: createFuture(time),
  284. builder: (BuildContext context, AsyncSnapshot<SportDetail> snapshot) {
  285. var _value = snapshot?.data;
  286. var _items = _createItems(type, _value?.recordsTodaySum);
  287. return snapshot.connectionState != ConnectionState.done
  288. ? RequestLoadingWidget()
  289. : Padding(
  290. padding: const EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 0),
  291. child: SingleChildScrollView(
  292. child: Column(
  293. children: <Widget>[
  294. SizedBox(
  295. height: 10.0,
  296. ),
  297. Padding(
  298. padding: const EdgeInsets.only(right: 12.0),
  299. child: CustomPaint(
  300. painter: Chart(
  301. type: TABS.indexOf(_tab.value),
  302. records: _value?.recordsToday
  303. ?.map((e) => ChartItem(type == 3 ? "${e.month}" : e.createdAt, e.consume))
  304. ?.toList() ??
  305. [],
  306. dateTime: time,
  307. drawMax: true,
  308. unit: "kal")
  309. ..initData(maxValue: 3500.0 * (type + 1), valueSplit: 500),
  310. child: Container(
  311. height: 200,
  312. ),
  313. ),
  314. ),
  315. const SizedBox(
  316. width: 12.0,
  317. ),
  318. Padding(
  319. padding: const EdgeInsets.all(8.0),
  320. child: StaggeredGridView.extent(
  321. maxCrossAxisExtent: (MediaQuery.of(context).size.width - 32.0) / 2,
  322. padding: EdgeInsets.zero,
  323. shrinkWrap: true,
  324. physics: NeverScrollableScrollPhysics(),
  325. mainAxisSpacing: 12.0,
  326. crossAxisSpacing: 12.0,
  327. children: _items
  328. .map((e) => Container(
  329. decoration: card(),
  330. padding: const EdgeInsets.fromLTRB(20.0, 20.0, 0, 20.0),
  331. child: Row(
  332. children: <Widget>[
  333. Image.asset(
  334. e.icon,
  335. width: 36.0,
  336. ),
  337. const SizedBox(
  338. width: 10.0,
  339. ),
  340. Column(
  341. crossAxisAlignment: CrossAxisAlignment.start,
  342. children: <Widget>[
  343. Row(
  344. crossAxisAlignment: CrossAxisAlignment.end,
  345. children: <Widget>[
  346. Text(
  347. e.title,
  348. style: Theme.of(context).textTheme.headline1.copyWith(fontSize: 20.0),
  349. strutStyle: fixedLine,
  350. ),
  351. Text(" ${e.unit}", style: Theme.of(context).textTheme.subtitle2),
  352. ],
  353. ),
  354. const SizedBox(
  355. width: 4.0,
  356. ),
  357. Text(e.subtitle, style: Theme.of(context).textTheme.bodyText1)
  358. ],
  359. )
  360. ],
  361. ),
  362. ))
  363. .toList(),
  364. staggeredTiles: _items.map((e) => StaggeredTile.fit(1)).toList()),
  365. ),
  366. if (type != 0 && _value?.recordsTodayAvg != null)
  367. Padding(
  368. padding: const EdgeInsets.fromLTRB(8.0, 0, 8.0, 20.0),
  369. child: Column(
  370. crossAxisAlignment: CrossAxisAlignment.start,
  371. children: <Widget>[
  372. Text(
  373. "日均数据",
  374. style: Theme.of(context).textTheme.headline1,
  375. ),
  376. SizedBox(
  377. height: 12.0,
  378. ),
  379. Container(
  380. padding: const EdgeInsets.fromLTRB(14.0, 21.0, 14.0, 21.0),
  381. decoration: card(),
  382. child: Column(
  383. children: <Widget>[
  384. Row(
  385. children: <Widget>[
  386. Image.asset(
  387. "lib/assets/img/day_icon_duration.png",
  388. width: 19.0,
  389. ),
  390. const SizedBox(
  391. width: 8.0,
  392. ),
  393. Expanded(
  394. child: Text(
  395. "日均时长",
  396. style: Theme.of(context).textTheme.subtitle1,
  397. ),
  398. ),
  399. SizedBox(
  400. width: 60.0,
  401. child: Text(
  402. "${_value?.recordsTodayAvg?.durationMinute ?? 0}分钟",
  403. style: Theme.of(context).textTheme.subtitle1,
  404. ),
  405. ),
  406. ],
  407. ),
  408. const SizedBox(
  409. height: 20.0,
  410. ),
  411. Row(
  412. children: <Widget>[
  413. Image.asset(
  414. "lib/assets/img/day_icon_consume.png",
  415. width: 19.0,
  416. ),
  417. const SizedBox(
  418. width: 8.0,
  419. ),
  420. Expanded(
  421. child: Text(
  422. "日均消耗",
  423. style: Theme.of(context).textTheme.subtitle1,
  424. ),
  425. ),
  426. SizedBox(
  427. width: 60.0,
  428. child: Text(
  429. "${_value?.recordsTodayAvg?.consume ?? 0}卡",
  430. style: Theme.of(context).textTheme.subtitle1,
  431. ),
  432. ),
  433. ],
  434. ),
  435. const SizedBox(
  436. height: 20.0,
  437. ),
  438. Row(
  439. children: <Widget>[
  440. Image.asset(
  441. "lib/assets/img/day_icon_frequency.png",
  442. width: 19.0,
  443. ),
  444. const SizedBox(
  445. width: 8.0,
  446. ),
  447. Expanded(
  448. child: Text(
  449. "日均运动次数",
  450. style: Theme.of(context).textTheme.subtitle1,
  451. ),
  452. ),
  453. SizedBox(
  454. width: 60.0,
  455. child: Text(
  456. "${_value?.recordsTodayAvg?.times ?? 0}次",
  457. style: Theme.of(context).textTheme.subtitle1,
  458. ),
  459. ),
  460. ],
  461. ),
  462. const SizedBox(
  463. height: 20.0,
  464. ),
  465. Row(
  466. children: <Widget>[
  467. Image.asset(
  468. "lib/assets/img/day_icon_steps.png",
  469. width: 19.0,
  470. ),
  471. const SizedBox(
  472. width: 8.0,
  473. ),
  474. Expanded(
  475. child: Text(
  476. "游戏步数",
  477. style: Theme.of(context).textTheme.subtitle1,
  478. ),
  479. ),
  480. SizedBox(
  481. width: 60.0,
  482. child: Text(
  483. "${_value?.recordsTodayAvg?.stepCount ?? 0}",
  484. style: Theme.of(context).textTheme.subtitle1,
  485. ),
  486. ),
  487. ],
  488. )
  489. ],
  490. ),
  491. )
  492. ],
  493. ),
  494. )
  495. ],
  496. ),
  497. ),
  498. );
  499. }));
  500. },
  501. ),
  502. ),
  503. ),
  504. Positioned(
  505. child: SafeArea(child: buildBackButton(context)),
  506. ),
  507. Positioned(
  508. right: 0,
  509. child: SafeArea(
  510. child: IconButton(
  511. icon: Image.asset("lib/assets/img/bbs_icon_share.png"),
  512. onPressed: () async {
  513. String hash;
  514. print("------------------------------------------------------------");
  515. await request(context, () async {
  516. ShareInfo _info = (await api.getshareCreateSport("day", 50.0)).data;
  517. hash = _info.hash;
  518. print("[hash:]---------------------------${_info.hash}");
  519. });
  520. if (hash != null) {
  521. NavigatorUtil.goPage(
  522. context,
  523. (context) => WebViewSharePage(
  524. "http://shoes-web.hiyd.com/share",
  525. hash: hash,
  526. openShare: true,
  527. ));
  528. }
  529. },
  530. ),
  531. ),
  532. ),
  533. ],
  534. ),
  535. );
  536. }
  537. int toType() {
  538. return TABS.indexOf(_tab.value);
  539. }
  540. void changeTab() async {
  541. _valueNotifierSportDetail.value = null;
  542. if (_valueNotifierDate.value == null) return;
  543. Future<SportDetail> data = createFuture(_valueNotifierDate.value);
  544. if (data != null) {
  545. data.then((value) => _valueNotifierSportDetail.value = value);
  546. }
  547. }
  548. Future<SportDetail> createQueryFuture(int offset) {
  549. int type = toType();
  550. DateTime next = offsetDate(type, offset);
  551. return createFuture(next);
  552. }
  553. Future<SportDetail> createFuture(DateTime time) async {
  554. int type = toType();
  555. return createFutureType(type, time);
  556. }
  557. Future<SportDetail> createFutureType(int type, DateTime time) async {
  558. Future<RespData<SportDetailSimple>> data;
  559. switch (type) {
  560. case 0:
  561. data = api.getSportRecordListOneDay('${time.year}-${'${time.month}'.padLeft(2, '0')}-${'${time.day}'.padLeft(2, '0')}');
  562. break;
  563. case 1:
  564. DateTime start = DateTime(time.year, time.month, time.day - time.weekday + 1);
  565. DateTime end = DateTime(time.year, time.month, time.day + 6 - time.weekday + 1);
  566. data = api.getSportRecordListByDay('${start.year}-${'${start.month}'.padLeft(2, '0')}-${'${start.day}'.padLeft(2, '0')}',
  567. '${end.year}-${'${end.month}'.padLeft(2, '0')}-${'${end.day}'.padLeft(2, '0')}');
  568. break;
  569. case 2:
  570. DateTime start = DateTime(time.year, time.month, 1);
  571. DateTime end = DateTime(time.year, time.month + 1, 0);
  572. data = api.getSportRecordListByDay('${start.year}-${'${start.month}'.padLeft(2, '0')}-${'${start.day}'.padLeft(2, '0')}',
  573. '${end.year}-${'${end.month}'.padLeft(2, '0')}-${'${end.day}'.padLeft(2, '0')}');
  574. break;
  575. case 3:
  576. data = api.getSportRecordListByMonth(time.year);
  577. break;
  578. }
  579. if (data != null) {
  580. var simple = await data;
  581. if (simple.code == 0) {
  582. return SportDetail(
  583. recordsTodaySum: simple.data.sum ?? RecordsTodaySum(consume: 0, duration: 0, crouchCount: 0, jumpCount: 0),
  584. recordsTodayAvg:
  585. simple.data.avg ?? RecordsTodaySum(consume: 0, duration: 0, crouchCount: 0, jumpCount: 0, durationMinute: 0, times: 0, stepCount: 0),
  586. recordsToday: simple.data.records);
  587. }
  588. }
  589. return null;
  590. }
  591. void rollDate(int offset) {
  592. if (_valueNotifierSportDetail.value == null) return;
  593. int type = toType();
  594. DateTime next = offsetDate(type, offset);
  595. _valueNotifierDate.value = next;
  596. // changeTab();
  597. }
  598. List<DataItem> _createItems(int type, RecordsTodaySum sum) {
  599. return [
  600. DataItem("lib/assets/img/data_icon_consume.png", "${sum?.consume ?? 0}", "卡", "消耗卡路里"),
  601. DataItem("lib/assets/img/data_icon_duration.png", "${sum?.durationMinute ?? 0}", "分钟", "总时长"),
  602. DataItem("lib/assets/img/data_icon_frequency.png", "${sum?.times ?? 0}", "次", "运动次数"),
  603. DataItem("lib/assets/img/data_icon_steps.png", "${sum?.stepCount ?? 0}", "歩", "运动步数"),
  604. DataItem("lib/assets/img/data_icon_squat.png", "${sum?.crouchRate?.toStringAsFixed(0) ?? 0}", "次/分钟", "下蹲频率"),
  605. DataItem("lib/assets/img/data_icon_jump.png", "${sum?.jumpRate?.toStringAsFixed(0) ?? 0}", "次/分钟", "跳跃频率"),
  606. if (type == 0) DataItem("lib/assets/img/data_icon_strength.png", "${strengthToLabel(sum?.consume ?? 0)}", "", "强度评级")
  607. ];
  608. }
  609. }
  610. class DataItem {
  611. final String icon, title, unit, subtitle;
  612. DataItem(this.icon, this.title, this.unit, this.subtitle);
  613. }
  614. class _Bg extends CustomPainter {
  615. final Paint _paint = Paint()..isAntiAlias = true;
  616. Color _color = const Color(0xffFFD736);
  617. @override
  618. void paint(Canvas canvas, Size size) {
  619. final Offset center = Offset(size.width / 2, size.height / 2);
  620. double radius = size.width / 2;
  621. // print("$size $center $radius");
  622. _paint.color = _color.withOpacity(.2);
  623. canvas.drawCircle(center, radius, _paint);
  624. _paint.color = _color.withOpacity(.5);
  625. canvas.drawCircle(center, radius - 10, _paint);
  626. _paint.color = _color;
  627. canvas.drawCircle(center, radius - 20, _paint);
  628. }
  629. @override
  630. bool shouldRepaint(CustomPainter oldDelegate) => false;
  631. }