sport_detail_page.dart 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928
  1. import 'dart:async';
  2. import 'dart:math';
  3. import 'dart:ui';
  4. import 'package:cached_network_image/cached_network_image.dart';
  5. import 'package:flutter/cupertino.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:flutter/painting.dart';
  8. import 'package:flutter_easyrefresh/easy_refresh.dart';
  9. import 'package:provider/provider.dart';
  10. import 'package:sport/bean/feed_back.dart';
  11. import 'package:sport/bean/game.dart';
  12. import 'package:sport/bean/share_info.dart';
  13. import 'package:sport/bean/sport_detail.dart';
  14. import 'package:sport/bean/sport_target.dart';
  15. import 'package:sport/bean/sport_target_today.dart';
  16. import 'package:sport/pages/home/step_page.dart';
  17. import 'package:sport/pages/home/target_modify.dart';
  18. import 'package:sport/pages/my/feedback_detail_page.dart';
  19. import 'package:sport/pages/my/level_page.dart';
  20. import 'package:sport/pages/social/share_webview.dart';
  21. import 'package:sport/provider/bluetooth.dart';
  22. import 'package:sport/provider/user_model.dart';
  23. import 'package:sport/router/navigator_util.dart';
  24. import 'package:sport/router/routes.dart';
  25. import 'package:sport/services/api/inject_api.dart';
  26. import 'package:sport/services/api/resp.dart';
  27. import 'package:sport/utils/date.dart';
  28. import 'package:sport/utils/toast.dart';
  29. import 'package:sport/widgets/appbar.dart';
  30. import 'package:sport/widgets/chart.dart';
  31. import 'package:sport/widgets/decoration.dart';
  32. import 'package:sport/widgets/dialog/alert_dialog.dart';
  33. import 'package:sport/widgets/dialog/request_dialog.dart';
  34. import 'package:sport/widgets/image.dart';
  35. import 'package:sport/widgets/label.dart';
  36. import 'package:sport/widgets/loading.dart';
  37. import 'package:sport/widgets/misc.dart';
  38. import 'package:sport/widgets/popmenu_bg.dart';
  39. import 'package:sport/widgets/space.dart';
  40. class SportDetailPage extends StatefulWidget {
  41. @override
  42. State<StatefulWidget> createState() => _PageState();
  43. }
  44. class _PageState extends State<SportDetailPage> with TickerProviderStateMixin, InjectApi, InjectLoginApi {
  45. final List<String> _TABS = ["日", "周", "月", "年"];
  46. ValueNotifier<String> _tab = ValueNotifier<String>("日");
  47. ValueNotifier<SportTargetToday> _valueNotifierTarget = ValueNotifier(null);
  48. ValueNotifier<SportDetail> _valueNotifierSportDetail = ValueNotifier(null);
  49. ValueNotifier<DateTime> _valueNotifierDate = ValueNotifier(DateTime.now());
  50. int _durationTarget = 60;
  51. SportDetail _sportDetail;
  52. PageController _pageController;
  53. int _page = 0;
  54. StreamSubscription streamSubscription;
  55. EasyRefreshController _easyRefreshController;
  56. @override
  57. void initState() {
  58. super.initState();
  59. _easyRefreshController = EasyRefreshController();
  60. _pageController = PageController(initialPage: 0)
  61. ..addListener(() {
  62. _page = _pageController.page.toInt();
  63. });
  64. streamSubscription = Provider.of<Bluetooth>(context, listen: false).queryStream.listen((event) {
  65. refreshData();
  66. });
  67. refreshData();
  68. Provider.of<Bluetooth>(context, listen: false).queryDeviceStep();
  69. }
  70. refreshData() {
  71. api.getSportDetail().then((value) {
  72. _sportDetail = value.data;
  73. if (_sportDetail == null) return;
  74. _durationTarget = value.data.durationTarget;
  75. _valueNotifierSportDetail.value = _sportDetail;
  76. _valueNotifierTarget.value = SportTargetToday(remain: value.data.durationTarget - value.data.durationTotalDay, target: value.data.target);
  77. if (mounted) setState(() {});
  78. if (value.data.target == null) {
  79. _showFirstTarget();
  80. }
  81. });
  82. }
  83. @override
  84. void dispose() {
  85. super.dispose();
  86. _pageController?.dispose();
  87. _tab?.dispose();
  88. _valueNotifierTarget?.dispose();
  89. _valueNotifierSportDetail?.dispose();
  90. _valueNotifierDate?.dispose();
  91. streamSubscription?.cancel();
  92. _easyRefreshController?.dispose();
  93. }
  94. @override
  95. Widget build(BuildContext context) {
  96. return Scaffold(
  97. body: Stack(
  98. children: <Widget>[
  99. EasyRefresh.builder(
  100. controller: _easyRefreshController,
  101. enableControlFinishRefresh: true,
  102. header: buildClassicalHeader(),
  103. onRefresh: () async {
  104. Provider.of<Bluetooth>(context, listen: false).queryDeviceStep();
  105. await Future.delayed(Duration(seconds: 5));
  106. _easyRefreshController.finishRefresh(success: true);
  107. },
  108. builder: (context, physics, header, footer) {
  109. return CustomScrollView(
  110. physics: physics,
  111. slivers: <Widget>[
  112. buildSliverAppBar(context, "运动详情", backgroundColor: Theme.of(context).scaffoldBackgroundColor, actions: <Widget>[
  113. PopupMenuTheme(
  114. data: PopupMenuThemeData(shape: PopmenuShape(borderRadius: BorderRadius.all(Radius.circular(10.0)))),
  115. child: PopupMenuButton(
  116. padding: EdgeInsets.zero,
  117. offset: Offset(0, kToolbarHeight / 2 + 15),
  118. icon: Image.asset("lib/assets/img/topbar_more.png"),
  119. onSelected: (action) async {
  120. switch (action) {
  121. case "modify":
  122. NavigatorUtil.go(context, Routes.targetModify).then((value) {
  123. if (value == null) return;
  124. if (value is SportTargetToday) {
  125. _valueNotifierTarget.value = value;
  126. Provider.of<UserModel>(context, listen: false).durationTarget.value = value.target.duration;
  127. }
  128. });
  129. break;
  130. case "data":
  131. FeedTypeInfoData group;
  132. await request(context, () async {
  133. FeedTypeInfo _feedTypeInfo = await loginApi.getFeedBackTypes();
  134. if (_feedTypeInfo.code == 0) {
  135. group = _feedTypeInfo.data.singleWhere((element) => element.groupId == '2');
  136. }
  137. });
  138. if (group != null) {
  139. print("[group:]---------------------------$group");
  140. NavigatorUtil.goPage(context, (context) => FeedbackDetailPage(group));
  141. }
  142. break;
  143. case "share":
  144. String hash;
  145. print("------------------------------------------------------------");
  146. await request(context, () async {
  147. ShareInfo _info = (await api.getshareCreateSport("day",50.0)).data;
  148. hash = _info.hash;
  149. print("[hash:]---------------------------${_info.hash}");
  150. });
  151. if(hash != null){
  152. NavigatorUtil.goPage(context, (context)=> WebViewSharePage("http://shoes-web.hiyd.com/share",hash: hash,));
  153. }
  154. break;
  155. }
  156. },
  157. itemBuilder: (_) => [
  158. menuItem("modify", "linkpop_icon_modify.png", "修改目标"),
  159. menuDivider(),
  160. menuItem("data", "linkpop_icon_modify_1.png", "数据报错"),
  161. menuDivider(),
  162. menuItem("share", "linkpop_icon_share.png", "数据分享"),
  163. ],
  164. ),
  165. )
  166. ]),
  167. header,
  168. SliverToBoxAdapter(
  169. child: _buildTargetWidget(),
  170. ),
  171. SliverToBoxAdapter(
  172. child: _buildDataWidget(),
  173. ),
  174. SliverToBoxAdapter(
  175. child: _buildAchievementWidget(),
  176. ),
  177. SliverToBoxAdapter(
  178. child: _buildSportListWidget(),
  179. ),
  180. ],
  181. );
  182. }),
  183. Positioned(
  184. right: -1,
  185. top: kToolbarHeight + MediaQuery.of(context).padding.top,
  186. child: GestureDetector(
  187. onTap: () => NavigatorUtil.goPage(context, (context) => StepPage()),
  188. child: Container(
  189. padding: EdgeInsets.fromLTRB(3, 3, 12, 3),
  190. decoration: BoxDecoration(
  191. color: Colors.white,
  192. borderRadius: BorderRadius.only(topLeft: Radius.circular(30), bottomLeft: Radius.circular(30)),
  193. border: Border.all(
  194. color: Theme.of(context).scaffoldBackgroundColor,
  195. width: .5,
  196. ),
  197. ),
  198. child: Row(
  199. children: <Widget>[
  200. Image.asset("lib/assets/img/gamedetail_step.png"),
  201. ValueListenableBuilder<SportDetail>(
  202. valueListenable: _valueNotifierSportDetail,
  203. builder: (_, _value, ___) => Text(
  204. " ${_value?.step ?? 0}步",
  205. style: Theme.of(context).textTheme.subtitle1,
  206. ))
  207. ],
  208. ),
  209. ),
  210. ),
  211. )
  212. ],
  213. ),
  214. );
  215. }
  216. Widget _buildTargetWidget() {
  217. return ValueListenableBuilder<SportTargetToday>(
  218. valueListenable: _valueNotifierTarget,
  219. builder: (_, _value, ___) => _value == null
  220. ? RequestLoadingWidget()
  221. : Column(
  222. children: <Widget>[
  223. Padding(
  224. padding: const EdgeInsets.only(right: 12.0, top: 15),
  225. child: Row(
  226. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  227. children: <Widget>[
  228. LabelWidget(title: "今日目标"),
  229. _value.target != null
  230. ? Row(
  231. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  232. children: <Widget>[
  233. Text("目标时长:${_value.target.duration}分钟\t\t\t\t强度:${_value.target.intension}", style: Theme.of(context).textTheme.bodyText1),
  234. ],
  235. )
  236. : InkWell(
  237. onTap: () {
  238. NavigatorUtil.go(context, Routes.targetModify).then((value) {
  239. if (value == null) return;
  240. if (value is SportTargetToday) {
  241. _valueNotifierTarget.value = value;
  242. }
  243. });
  244. },
  245. child: Row(
  246. mainAxisAlignment: MainAxisAlignment.end,
  247. children: <Widget>[
  248. Text("设立目标", style: Theme.of(context).textTheme.subtitle1.copyWith(color: Theme.of(context).accentColor)),
  249. Space(
  250. width: 5,
  251. ),
  252. arrowRight6()
  253. ],
  254. ),
  255. ),
  256. ],
  257. ),
  258. ),
  259. Container(
  260. margin: EdgeInsets.fromLTRB(12.0, 6.0, 12.0, 18.0),
  261. padding: EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0),
  262. decoration: circular(),
  263. child: Column(
  264. crossAxisAlignment: CrossAxisAlignment.start,
  265. children: <Widget>[
  266. Text("运动时长", style: Theme.of(context).textTheme.subtitle1),
  267. Space(
  268. height: 12,
  269. ),
  270. Container(child: LayoutBuilder(builder: (context, box) {
  271. int minute = _sportDetail.durationTotalDay;
  272. double percent = minute / (_value.target == null ? _durationTarget : _value.target.duration);
  273. double w = 70;
  274. double padding = box.maxWidth * percent - w / 2;
  275. if (padding < w / 2) {
  276. padding = 0;
  277. }
  278. if (padding > box.maxWidth - w) {
  279. padding = box.maxWidth - w;
  280. }
  281. // fix 一直在最左!
  282. padding = 0;
  283. return Column(
  284. crossAxisAlignment: CrossAxisAlignment.start,
  285. children: <Widget>[
  286. Padding(
  287. padding: EdgeInsets.only(left: padding),
  288. child: Container(
  289. width: w,
  290. alignment: padding == 0 ? Alignment.centerLeft : Alignment.center,
  291. child: RichText(
  292. text: TextSpan(style: Theme.of(context).textTheme.subtitle2, children: <InlineSpan>[
  293. TextSpan(
  294. text: '$minute',
  295. style: Theme.of(context).textTheme.subtitle2.copyWith(fontWeight: FontWeight.bold, fontSize: 25),
  296. ),
  297. TextSpan(text: '分钟'),
  298. ]),
  299. ),
  300. ),
  301. ),
  302. Space(
  303. height: 4,
  304. ),
  305. CustomPaint(
  306. painter: _ProgressBar(percent),
  307. child: Container(
  308. height: 12,
  309. ),
  310. ),
  311. ],
  312. );
  313. })),
  314. Space(
  315. height: 15,
  316. ),
  317. Row(
  318. children: <Widget>[
  319. Text(
  320. "消耗(kal)",
  321. style: Theme.of(context).textTheme.subtitle2,
  322. ),
  323. Space(
  324. width: 6,
  325. ),
  326. Text(
  327. "${_sportDetail?.recordsTodaySum?.consume ?? 0}",
  328. style: Theme.of(context).textTheme.headline1.copyWith(color: Theme.of(context).accentColor),
  329. ),
  330. Space(
  331. width: 30,
  332. ),
  333. Text(
  334. "累计运动 (次)",
  335. style: Theme.of(context).textTheme.subtitle2,
  336. ),
  337. Space(
  338. width: 6,
  339. ),
  340. Text(
  341. "${_sportDetail?.recordsTodaySum?.times ?? 0}",
  342. style: Theme.of(context).textTheme.headline1.copyWith(color: Theme.of(context).accentColor),
  343. ),
  344. ],
  345. ),
  346. SizedBox(
  347. height: 4,
  348. ),
  349. Divider(),
  350. SizedBox(
  351. height: 4,
  352. ),
  353. if (_sportDetail?.nextAchievement != null)
  354. InkWell(
  355. onTap: () => NavigatorUtil.goAchievementDetails(context, id: _sportDetail.nextAchievement.achievement.id),
  356. child: Row(
  357. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  358. children: <Widget>[
  359. RichText(
  360. text: TextSpan(style: Theme.of(context).textTheme.bodyText1, children: <InlineSpan>[
  361. TextSpan(text: '距离'),
  362. TextSpan(
  363. text: ' ${_sportDetail.nextAchievement.achievement?.name} ',
  364. style: Theme.of(context).textTheme.bodyText1.copyWith(color: Theme.of(context).accentColor),
  365. ),
  366. TextSpan(
  367. text: '还需运动 ${_sportDetail.nextAchievement.diff ?? 0}${_sportDetail.nextAchievement.achievement?.conditionMeasure ?? ''}'),
  368. ])),
  369. arrowRight4()
  370. ],
  371. ),
  372. ),
  373. ],
  374. ),
  375. ),
  376. ],
  377. ),
  378. );
  379. }
  380. Widget _buildDataWidget() {
  381. return Column(
  382. children: <Widget>[
  383. LabelWidget(title: "数据统计"),
  384. Container(
  385. margin: EdgeInsets.fromLTRB(12.0, 6.0, 12.0, 18.0),
  386. padding: EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0),
  387. decoration: circular(),
  388. child: Column(
  389. children: <Widget>[
  390. ValueListenableBuilder(
  391. valueListenable: _tab,
  392. builder: (BuildContext context, String value, Widget child) {
  393. return Column(
  394. children: <Widget>[
  395. Row(
  396. mainAxisAlignment: MainAxisAlignment.center,
  397. mainAxisSize: MainAxisSize.min,
  398. children: ["日", "/", "周", "/", "月", "/", "年"]
  399. .map((e) => e == "/"
  400. ? Container(
  401. margin: EdgeInsets.symmetric(horizontal: 6.0),
  402. color: Color(0xffdcdcdc),
  403. width: 0.5,
  404. height: 14,
  405. transform: Matrix4.rotationZ(0.35),
  406. )
  407. : InkWell(
  408. onTap: () {
  409. if (_valueNotifierSportDetail.value == null) return;
  410. _tab.value = e;
  411. _valueNotifierDate.value = DateTime.now();
  412. // _pageController = PageController(initialPage: 0);
  413. _pageController.jumpToPage(0);
  414. changeTab(DateTime.now());
  415. },
  416. child: Container(
  417. margin: EdgeInsets.symmetric(horizontal: 12.0),
  418. padding: EdgeInsets.all(8.0),
  419. decoration: value == e ? BoxDecoration(color: Theme.of(context).accentColor, shape: BoxShape.circle) : null,
  420. child: Text(
  421. "$e",
  422. style: Theme.of(context).textTheme.subtitle1.copyWith(color: value == e ? Colors.white : Color(0xff333333)),
  423. ),
  424. )))
  425. .toList(),
  426. ),
  427. ],
  428. );
  429. },
  430. ),
  431. ValueListenableBuilder(
  432. valueListenable: _tab,
  433. builder: (_, time, ___) => Container(
  434. height: 520,
  435. child: PageView.builder(
  436. reverse: true,
  437. itemCount: 10240,
  438. controller: _pageController,
  439. onPageChanged: (page) {},
  440. itemBuilder: (context, index) {
  441. var time = offsetDate(toType(), -index);
  442. return FutureBuilder<SportDetail>(
  443. future: changeTab(time),
  444. builder: (BuildContext context, AsyncSnapshot<SportDetail> snapshot) {
  445. SportDetail _value = snapshot.data;
  446. return snapshot.connectionState != ConnectionState.done
  447. ? RequestLoadingWidget()
  448. : Column(
  449. children: <Widget>[
  450. Padding(
  451. padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 12),
  452. child: Row(
  453. mainAxisAlignment: MainAxisAlignment.center,
  454. children: <Widget>[
  455. GestureDetector(
  456. behavior: HitTestBehavior.opaque,
  457. onTap: () {
  458. _pageController?.nextPage(duration: Duration(milliseconds: 500), curve: Curves.linear);
  459. },
  460. child: Padding(
  461. padding: const EdgeInsets.all(16.0),
  462. child: arrowLeft(),
  463. ),
  464. ),
  465. Expanded(
  466. child: Center(
  467. child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
  468. int type = toType();
  469. if (type == 0) {
  470. return Text(
  471. "${time.year}.${'${time.month}'.padLeft(2, '0')}.${'${time.day}'.padLeft(2, '0')} ${WEEK[time.weekday == 0 ? 6 : time.weekday - 1]}");
  472. } else if (type == 1) {
  473. DateTime start = DateTime(time.year, time.month, time.day - time.weekday + 1);
  474. DateTime end = DateTime(time.year, time.month, time.day + 6 - time.weekday + 1);
  475. print("$time ${time.weekday} == $start $end");
  476. return Text(
  477. "${start.year}.${'${start.month}'.padLeft(2, '0')}.${'${start.day}'.padLeft(2, '0')} 至 ${end.year}.${'${end.month}'.padLeft(2, '0')}.${'${end.day}'.padLeft(2, '0')}");
  478. } else if (type == 2) {
  479. return Text("${time.year}年${'${time.month}'.padLeft(2, '0')}月");
  480. } else if (type == 3) {
  481. return Text("${time.year}年");
  482. }
  483. return Text(" ");
  484. }),
  485. ),
  486. ),
  487. GestureDetector(
  488. behavior: HitTestBehavior.opaque,
  489. onTap: () {
  490. if (_pageController?.page == 0.0) {
  491. return;
  492. }
  493. _pageController?.previousPage(duration: Duration(milliseconds: 500), curve: Curves.linear);
  494. },
  495. child: Padding(
  496. padding: const EdgeInsets.all(16.0),
  497. child: arrowRight(),
  498. ),
  499. ),
  500. ],
  501. ),
  502. ),
  503. Space(
  504. height: 12,
  505. ),
  506. CustomPaint(
  507. painter: Chart(type: _TABS.indexOf(_tab.value),records: _value?.recordsToday?.map((e) => ChartItem(e.createdAt, e.consume))?.toList(), dateTime: time)..initData(),
  508. child: Container(
  509. height: 160,
  510. ),
  511. ),
  512. Space(
  513. height: 6,
  514. ),
  515. Column(
  516. children: <Widget>[
  517. Row(
  518. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  519. children: <Widget>[
  520. Text(
  521. "总消耗",
  522. style: Theme.of(context).textTheme.subtitle1,
  523. ),
  524. Text("${_value == null ? ".." : "${_value.recordsTodaySum.consume}"} kal",
  525. style: Theme.of(context).textTheme.subtitle1)
  526. ],
  527. ),
  528. Space(
  529. height: 8,
  530. ),
  531. Row(
  532. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  533. children: <Widget>[
  534. Text(
  535. "总时长",
  536. style: Theme.of(context).textTheme.subtitle1,
  537. ),
  538. Text("${_value == null ? ".." : "${_value.recordsTodaySum.duration}"}分钟",
  539. style: Theme.of(context).textTheme.subtitle1)
  540. ],
  541. ),
  542. Space(
  543. height: 8,
  544. ),
  545. Row(
  546. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  547. children: <Widget>[
  548. Text(
  549. "累计运动",
  550. style: Theme.of(context).textTheme.subtitle1,
  551. ),
  552. Text("${_value == null ? ".." : "${_value.recordsTodaySum.times ?? 0}"}次",
  553. style: Theme.of(context).textTheme.subtitle1)
  554. ],
  555. ),
  556. Space(
  557. height: 8,
  558. ),
  559. Row(
  560. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  561. children: <Widget>[
  562. Text(
  563. "跳跃次数",
  564. style: Theme.of(context).textTheme.subtitle1,
  565. ),
  566. Text("${_value == null ? ".." : "${_value.recordsTodaySum.jumpCount}"}次",
  567. style: Theme.of(context).textTheme.subtitle1)
  568. ],
  569. ),
  570. Space(
  571. height: 8,
  572. ),
  573. Row(
  574. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  575. children: <Widget>[
  576. Text(
  577. "下蹲次数",
  578. style: Theme.of(context).textTheme.subtitle1,
  579. ),
  580. Text("${_value == null ? ".." : "${_value.recordsTodaySum.crouchCount}"}次",
  581. style: Theme.of(context).textTheme.subtitle1)
  582. ],
  583. ),
  584. Space(
  585. height: 8,
  586. ),
  587. Row(
  588. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  589. children: <Widget>[
  590. Text(
  591. "游戏步数",
  592. style: Theme.of(context).textTheme.subtitle1,
  593. ),
  594. Text("${_value == null ? ".." : "${_value.recordsTodaySum?.stepCount ?? 0}"}次",
  595. style: Theme.of(context).textTheme.subtitle1)
  596. ],
  597. ),
  598. Row(
  599. children: <Widget>[
  600. Expanded(child: Divider()),
  601. Padding(
  602. padding: const EdgeInsets.all(16.0),
  603. child: Text(
  604. "相当于",
  605. style: Theme.of(context).textTheme.bodyText1,
  606. ),
  607. ),
  608. Expanded(child: Divider())
  609. ],
  610. ),
  611. sportBeEquivalentTo(
  612. context, _value == null || _value.recordsTodaySum == null ? 0 : _value.recordsTodaySum.consume),
  613. ],
  614. ),
  615. Space(
  616. height: 10,
  617. )
  618. ],
  619. );
  620. });
  621. },
  622. ),
  623. )),
  624. ],
  625. ),
  626. ),
  627. ],
  628. );
  629. }
  630. Widget _buildAchievementWidget() {
  631. return Column(
  632. children: <Widget>[
  633. Row(
  634. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  635. children: <Widget>[
  636. LabelWidget(title: "我的成就"),
  637. GestureDetector(
  638. behavior: HitTestBehavior.opaque,
  639. onTap: () {
  640. NavigatorUtil.goPage(context, (context) => LevelPage());
  641. },
  642. child: Padding(
  643. padding: const EdgeInsets.fromLTRB(12.0, 6.0, 12.0, 6.0),
  644. child: Row(
  645. children: <Widget>[
  646. Text(
  647. "更多",
  648. style: Theme.of(context).textTheme.bodyText1,
  649. ),
  650. Space(
  651. width: 4,
  652. ),
  653. arrowRight4()
  654. ],
  655. ),
  656. ),
  657. )
  658. ],
  659. ),
  660. Container(
  661. margin: EdgeInsets.fromLTRB(12.0, 6.0, 12.0, 18.0),
  662. padding: EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0),
  663. width: double.infinity,
  664. decoration: circular(),
  665. child: Padding(
  666. padding: const EdgeInsets.symmetric(vertical: 12.0),
  667. child: achievementGroupWidget(_sportDetail?.achievements),
  668. ),
  669. ),
  670. ],
  671. );
  672. }
  673. Widget _buildSportListWidget() {
  674. return _sportDetail?.gamesSum?.isNotEmpty == true
  675. ? Column(
  676. children: <Widget>[
  677. LabelWidget(title: "运动统计"),
  678. Container(
  679. margin: EdgeInsets.fromLTRB(12.0, 6.0, 12.0, 18.0),
  680. padding: EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0),
  681. decoration: circular(),
  682. child: ListView.separated(
  683. padding: EdgeInsets.zero,
  684. shrinkWrap: true,
  685. physics: NeverScrollableScrollPhysics(),
  686. itemBuilder: (context, index) {
  687. GamesSum sum = _sportDetail.gamesSum[index];
  688. return Column(
  689. children: <Widget>[
  690. _buildWrapListHeaderWidget(sum),
  691. _buildWrapListTileWidget("总消耗:", "${sum.consumeTotal}kal"),
  692. _buildWrapListTileWidget("总时长:", "${sum.durationTotal}分钟"),
  693. _buildWrapListTileWidget("运动效率:", "${(sum.consumeTotal / sum.durationTotal).toStringAsFixed(2)}kal/min"),
  694. _buildWrapListTileWidget("总局数:", "${sum.playCount}局"),
  695. _buildWrapListTileWidget("最高得分:", "${sum.scoreMax.toStringAsFixed(1)}"),
  696. Padding(
  697. padding: const EdgeInsets.all(8.0),
  698. child: InkWell(
  699. onTap: () => NavigatorUtil.goGameHistory(context, sum.game),
  700. child: Row(
  701. mainAxisSize: MainAxisSize.min,
  702. children: <Widget>[
  703. Text(
  704. "查看记录详情 ",
  705. style: Theme.of(context).textTheme.bodyText1,
  706. ),
  707. arrowRight4()
  708. ],
  709. ),
  710. ),
  711. )
  712. ],
  713. );
  714. },
  715. separatorBuilder: (context, index) => Divider(),
  716. itemCount: _sportDetail.gamesSum.length),
  717. ),
  718. ],
  719. )
  720. : Container();
  721. }
  722. Widget _buildWrapListHeaderWidget(GamesSum sum) {
  723. GameInfoData game = sum.game;
  724. return Row(
  725. children: <Widget>[
  726. Container(
  727. width: 50,
  728. height: 50,
  729. margin: EdgeInsets.fromLTRB(0, 6, 8, 6),
  730. decoration:
  731. BoxDecoration(borderRadius: BorderRadius.circular(6), image: DecorationImage(fit: BoxFit.cover, image: CachedNetworkImageProvider(game.cover))),
  732. ),
  733. Column(
  734. crossAxisAlignment: CrossAxisAlignment.start,
  735. children: <Widget>[
  736. Text(
  737. game.name,
  738. style: Theme.of(context).textTheme.headline3,
  739. ),
  740. Space(
  741. height: 5,
  742. ),
  743. Text(
  744. "最近游玩:${sum.lastPlayAt}",
  745. style: Theme.of(context).textTheme.bodyText1,
  746. )
  747. ],
  748. )
  749. ],
  750. );
  751. }
  752. Widget _buildWrapListTileWidget(String key, String value) {
  753. return Padding(
  754. padding: const EdgeInsets.symmetric(vertical: 4.0),
  755. child: Row(
  756. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  757. children: <Widget>[
  758. Text(
  759. key,
  760. style: Theme.of(context).textTheme.subtitle1,
  761. ),
  762. Text(
  763. value,
  764. style: Theme.of(context).textTheme.subtitle1,
  765. )
  766. ],
  767. ),
  768. );
  769. }
  770. int toType() {
  771. return _TABS.indexOf(_tab.value);
  772. }
  773. Future<SportDetail> changeTab(DateTime time) async {
  774. int type = toType();
  775. Future<RespData<SportDetailSimple>> data;
  776. switch (type) {
  777. case 0:
  778. data = api.getSportRecordListOneDay('${time.year}-${'${time.month}'.padLeft(2, '0')}-${'${time.day}'.padLeft(2, '0')}');
  779. break;
  780. case 1:
  781. DateTime start = DateTime(time.year, time.month, time.day - time.weekday + 1);
  782. DateTime end = DateTime(time.year, time.month, time.day + 6 - time.weekday + 1);
  783. data = api.getSportRecordListByDay('${start.year}-${'${start.month}'.padLeft(2, '0')}-${'${start.day}'.padLeft(2, '0')}',
  784. '${end.year}-${'${end.month}'.padLeft(2, '0')}-${'${end.day}'.padLeft(2, '0')}');
  785. break;
  786. case 2:
  787. DateTime start = DateTime(time.year, time.month, 1);
  788. DateTime end = DateTime(time.year, time.month + 1, 0);
  789. data = api.getSportRecordListByDay('${start.year}-${'${start.month}'.padLeft(2, '0')}-${'${start.day}'.padLeft(2, '0')}',
  790. '${end.year}-${'${end.month}'.padLeft(2, '0')}-${'${end.day}'.padLeft(2, '0')}');
  791. break;
  792. case 3:
  793. data = api.getSportRecordListByMonth(time.year);
  794. break;
  795. }
  796. if (data != null) {
  797. var simple = await data;
  798. if (simple.code == 0) {
  799. return SportDetail(
  800. recordsTodaySum: simple.data.sum ?? RecordsTodaySum(consume: 0, duration: 0, crouchCount: 0, jumpCount: 0), recordsToday: simple.data.records);
  801. }
  802. }
  803. return null;
  804. }
  805. void _showFirstTarget() async {
  806. SportTarget sportTarget;
  807. var result = await showDialog(
  808. context: context,
  809. barrierDismissible: false,
  810. builder: (context) => NotificationListener<TargetNotification>(
  811. onNotification: (notification) {
  812. sportTarget = notification.target;
  813. return true;
  814. },
  815. child: CustomAlertDialog(
  816. addClose: true,
  817. title: '运动目标设置',
  818. child: Column(
  819. children: <Widget>[
  820. Align(
  821. alignment: Alignment.centerLeft,
  822. child: Padding(
  823. padding: const EdgeInsets.only(left: 20.0),
  824. child: Text(
  825. "请选择每日运动目标",
  826. style: Theme.of(context).textTheme.subtitle1,
  827. ),
  828. ),
  829. ),
  830. Padding(
  831. padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 5.0),
  832. child: TargetModify(
  833. editCustom: false,
  834. padding: false,
  835. ),
  836. )
  837. ],
  838. ),
  839. ok: () async {
  840. if (sportTarget == null) {
  841. return;
  842. }
  843. RespData<SportTargetToday> resp = (sportTarget.name == '自定义'
  844. ? await api.setSportTargetCustom(sportTarget.duration, sportTarget.consume)
  845. : await api.setSportTarget(sportTarget.id));
  846. if (resp.code == 0) {
  847. Navigator.of(context).pop(resp.data);
  848. } else {
  849. ToastUtil.show(resp.msg);
  850. }
  851. },
  852. )));
  853. if (result is SportTargetToday) {
  854. if (result != null) {
  855. setState(() {
  856. _valueNotifierTarget.value = result;
  857. });
  858. Provider.of<UserModel>(context, listen: false).durationTarget.value = result.target.duration;
  859. }
  860. }
  861. }
  862. }
  863. class _ProgressBar extends CustomPainter {
  864. final Paint _paint = Paint()
  865. ..color = Color(0xffeeeeee)
  866. ..isAntiAlias = true;
  867. final Paint _indicatorPaint = Paint()
  868. ..color = Color(0xffFFC400)
  869. ..isAntiAlias = true;
  870. double _paddingBar = 2;
  871. double percent;
  872. _ProgressBar(this.percent);
  873. @override
  874. void paint(Canvas canvas, Size size) {
  875. double indicator = size.width * min(1.0, this.percent);
  876. canvas.save();
  877. var rect = Rect.fromLTRB(0, 0, size.width, size.height);
  878. canvas.clipRRect(RRect.fromRectAndRadius(rect, Radius.circular(size.height / 2)), doAntiAlias: true);
  879. canvas.drawRect(rect, _paint);
  880. Paint _valuePaint = Paint()
  881. ..shader = LinearGradient(
  882. begin: Alignment.centerLeft,
  883. end: Alignment.centerRight,
  884. colors: <Color>[Color(0xffFFE600), Color(0xffFF9100)],
  885. ).createShader(rect);
  886. canvas.drawRect(Rect.fromLTRB(0, 0, size.width * this.percent, size.height), _valuePaint);
  887. canvas.restore();
  888. // Path path = Path()
  889. // ..moveTo(indicator, size.height - 13 - _paddingBar)
  890. // ..lineTo(indicator - 5, 0)
  891. // ..lineTo(indicator + 5, 0)
  892. // ..close();
  893. // canvas.drawPath(path, _indicatorPaint);
  894. }
  895. @override
  896. bool shouldRepaint(CustomPainter oldDelegate) {
  897. return false;
  898. }
  899. }