consume_page.dart 38 KB

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