step_page.dart 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. import 'dart:convert';
  2. import 'dart:math';
  3. import 'dart:ui';
  4. import 'dart:ui' as ui;
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter/painting.dart';
  7. import 'package:sport/bean/sport_step.dart';
  8. import 'package:sport/pages/home/step_realtime_page.dart';
  9. import 'package:sport/router/navigator_util.dart';
  10. import 'package:sport/services/Converter.dart';
  11. import 'package:sport/services/api/inject_api.dart';
  12. import 'package:sport/services/api/resp.dart';
  13. import 'package:sport/utils/date.dart';
  14. import 'package:sport/utils/toast.dart';
  15. import 'package:sport/widgets/appbar.dart';
  16. import 'package:sport/widgets/chart.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. import 'package:sport/widgets/space.dart';
  22. const Color _color = Color(0xffFFC400);
  23. const List<String> TABS = ["日", "周", "月", "年"];
  24. class StepPage extends StatefulWidget {
  25. @override
  26. State<StatefulWidget> createState() => _PageState();
  27. }
  28. class _PageState extends State<StepPage> with TickerProviderStateMixin, InjectApi, InjectLoginApi {
  29. ValueNotifier<String> _tab = ValueNotifier<String>("日");
  30. ValueNotifier<SportStep> _valueNotifierSportDetail = ValueNotifier(null);
  31. ValueNotifier<DateTime> _valueNotifierDate = ValueNotifier(DateTime.now());
  32. PageController _pageController;
  33. @override
  34. void initState() {
  35. super.initState();
  36. _pageController = PageController(initialPage: 0);
  37. var now = DateTime.now();
  38. // api.addDaily(
  39. // step: Random().nextInt(255), distance: Random().nextInt(255) * 10, time: '${now.year}-${now.month}-${now.day} ${now.hour}:${now.minute}:${now.second}');
  40. // api.addDaily(data: json.encode([[Random().nextInt(255), Random().nextInt(255),DateTime.now().millisecondsSinceEpoch ~/ 1000 - Random().nextInt(1000)],[Random().nextInt(255), Random().nextInt(255),DateTime.now().millisecondsSinceEpoch ~/ 1000 - Random().nextInt(1000)],[Random().nextInt(255), Random().nextInt(255),DateTime.now().millisecondsSinceEpoch ~/ 1000 - Random().nextInt(1000)]]));
  41. changeTab();
  42. }
  43. @override
  44. void dispose() {
  45. _pageController?.dispose();
  46. super.dispose();
  47. _tab?.dispose();
  48. _valueNotifierSportDetail?.dispose();
  49. _valueNotifierDate?.dispose();
  50. }
  51. @override
  52. Widget build(BuildContext context) {
  53. return Scaffold(
  54. backgroundColor: Colors.white,
  55. appBar: buildAppBar(
  56. context,
  57. title: "运动步数",
  58. actions: <Widget>[
  59. IconButton(
  60. icon: Image.asset(
  61. "lib/assets/img/runsteps_icon.png",
  62. width: 22.0,
  63. ),
  64. onPressed: () => NavigatorUtil.goPage(context, (context) => StepRealTimePage()),
  65. )
  66. ],
  67. ),
  68. body: Container(
  69. color: Colors.white,
  70. child: ValueListenableBuilder(
  71. valueListenable: _tab,
  72. builder: (BuildContext context, String value, Widget child) {
  73. return Column(
  74. children: <Widget>[
  75. Padding(
  76. padding: const EdgeInsets.all(8.0),
  77. child: Row(
  78. mainAxisAlignment: MainAxisAlignment.center,
  79. children: ["日", "/", "周", "/", "月", "/", "年"]
  80. .map((e) => e == "/"
  81. ? Container(
  82. margin: const EdgeInsets.fromLTRB(5, 0, 1, 0),
  83. color: const Color(0xffdcdcdc),
  84. width: 0.5,
  85. height: 14,
  86. transform: Matrix4.rotationZ(0.35),
  87. )
  88. : InkWell(
  89. onTap: () {
  90. if (_valueNotifierSportDetail.value == null) return;
  91. _tab.value = e;
  92. _valueNotifierDate.value = DateTime.now();
  93. _pageController.jumpToPage(0);
  94. // _pageController= PageController(initialPage: 0);
  95. changeTab();
  96. },
  97. child: Container(
  98. margin: EdgeInsets.symmetric(horizontal: 12.0),
  99. padding: EdgeInsets.all(8.0),
  100. decoration: value == e ? BoxDecoration(color: _color, shape: BoxShape.circle) : null,
  101. child: Text(
  102. "$e",
  103. style: value == e
  104. ? Theme.of(context).textTheme.subtitle1.copyWith(color: Colors.white)
  105. : Theme.of(context).textTheme.subtitle1,
  106. ),
  107. )))
  108. .toList(),
  109. ),
  110. ),
  111. Center(
  112. child: ValueListenableBuilder<DateTime>(
  113. valueListenable: _valueNotifierDate,
  114. builder: (_, time, ___) {
  115. int type = toType();
  116. String text = "";
  117. if (type == 0) {
  118. text = "${time.year}.${'${time.month}'.padLeft(2, '0')}.${'${time.day}'.padLeft(2, '0')} 6:00 - 24:00 ";
  119. } else if (type == 1) {
  120. DateTime start = DateTime(time.year, time.month, time.day - time.weekday + 1);
  121. DateTime end = DateTime(time.year, time.month, time.day + 6 - time.weekday + 1);
  122. print("$time ${time.weekday} == $start $end");
  123. text =
  124. "${start.year}.${'${start.month}'.padLeft(2, '0')}.${'${start.day}'.padLeft(2, '0')} 至 ${end.year}.${'${end.month}'.padLeft(2, '0')}.${'${end.day}'.padLeft(2, '0')}";
  125. } else if (type == 2) {
  126. text = ("${time.year}年${'${time.month}'.padLeft(2, '0')}月");
  127. } else if (type == 3) {
  128. text = ("${time.year}年");
  129. }
  130. return Row(
  131. mainAxisSize: MainAxisSize.min,
  132. children: <Widget>[
  133. GestureDetector(
  134. behavior: HitTestBehavior.opaque,
  135. onTap: () {
  136. _pageController?.nextPage(duration: Duration(milliseconds: 500), curve: Curves.linear);
  137. },
  138. child: Padding(
  139. padding: const EdgeInsets.all(18.0),
  140. child: arrowLeft(),
  141. ),
  142. ),
  143. Text(
  144. text,
  145. style: Theme.of(context).textTheme.bodyText2.copyWith(color: Color(0xff333333)),
  146. strutStyle: fixedLine,
  147. ),
  148. GestureDetector(
  149. behavior: HitTestBehavior.opaque,
  150. onTap: () {
  151. if (_pageController?.page == 0.0) {
  152. ToastUtil.show("没有数据了");
  153. return;
  154. }
  155. _pageController?.previousPage(duration: Duration(milliseconds: 500), curve: Curves.linear);
  156. },
  157. child: Padding(
  158. padding: const EdgeInsets.all(18.0),
  159. child: arrowRight(),
  160. ),
  161. ),
  162. ],
  163. );
  164. }),
  165. ),
  166. Expanded(
  167. child: PageView.builder(
  168. reverse: true,
  169. itemCount: 10240,
  170. controller: _pageController,
  171. onPageChanged: (page) {
  172. rollDate(-page);
  173. },
  174. itemBuilder: (context, index) {
  175. int type = toType();
  176. DateTime time = offsetDate(type, -index);
  177. return FutureBuilder<RespData<SportStep>>(
  178. future: createFuture(time),
  179. builder: (BuildContext context, AsyncSnapshot<RespData<SportStep>> snapshot) {
  180. var _value = snapshot?.data?.data;
  181. return snapshot.connectionState != ConnectionState.done
  182. ? RequestLoadingWidget()
  183. : Padding(
  184. padding: const EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 0),
  185. child: SingleChildScrollView(
  186. child: Column(
  187. children: <Widget>[
  188. Text(
  189. "${_value?.sum?.stepDayAvg ?? _value?.sum?.step ?? 0}",
  190. style: Theme.of(context).textTheme.headline1.copyWith(fontSize: 26.0, color: _color),
  191. ),
  192. Text(
  193. toType() == 0 ? "日总步数" : "日均步数",
  194. style: Theme.of(context).textTheme.subtitle2.copyWith(color: Color(0xff999999)),
  195. ),
  196. SizedBox(
  197. height: 30.0,
  198. ),
  199. Padding(
  200. padding: const EdgeInsets.only(right: 12.0),
  201. child: CustomPaint(
  202. painter: Chart(
  203. type: TABS.indexOf(_tab.value),
  204. records: snapshot.data?.data?.records
  205. ?.map((e) => ChartItem(type == 3 ? "${e.month}" : e.createdAt, e.step))
  206. ?.toList() ??
  207. [],
  208. dateTime: time,
  209. drawMax: true,
  210. unit: "歩")
  211. ..initData(maxValue: 7000.0 * (type + 1)),
  212. child: Container(
  213. height: 200,
  214. ),
  215. ),
  216. ),
  217. Padding(
  218. padding: const EdgeInsets.all(12.0),
  219. child: Column(
  220. crossAxisAlignment: CrossAxisAlignment.start,
  221. children: <Widget>[
  222. Space(
  223. height: 10,
  224. ),
  225. Text(
  226. "运动总距离",
  227. style: Theme.of(context).textTheme.subtitle1,
  228. ),
  229. Space(
  230. height: 10,
  231. ),
  232. RichText(
  233. text: TextSpan(style: Theme.of(context).textTheme.subtitle2, children: <InlineSpan>[
  234. TextSpan(
  235. text:
  236. '${_value == null ? ".." : "${((_value.sum.stepDaily + _value.sum.stepGame) * .6).toStringAsFixed(1)}"}',
  237. style: Theme.of(context).textTheme.subtitle2.copyWith(fontWeight: FontWeight.bold, fontSize: 25)),
  238. TextSpan(text: ' 米', style: Theme.of(context).textTheme.subtitle1),
  239. ]),
  240. ),
  241. Divider(
  242. height: 24,
  243. ),
  244. if (type != 0)
  245. Row(
  246. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  247. children: <Widget>[
  248. Text(
  249. type == 1 ? "周总步数" : type == 2 ? "月总步数" : "年总步数",
  250. style: Theme.of(context).textTheme.subtitle1,
  251. ),
  252. Text("${_value == null ? ".." : "${_value.sum.stepDaily + _value.sum.stepGame}"}",
  253. style: Theme.of(context).textTheme.subtitle1)
  254. ],
  255. ),
  256. if (type != 0)
  257. Space(
  258. height: 8,
  259. ),
  260. Row(
  261. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  262. children: <Widget>[
  263. Text(
  264. "日常步数",
  265. style: Theme.of(context).textTheme.subtitle1,
  266. ),
  267. Text("${_value == null ? ".." : "${_value.sum.stepDaily}"}", style: Theme.of(context).textTheme.subtitle1)
  268. ],
  269. ),
  270. Space(
  271. height: 8,
  272. ),
  273. Row(
  274. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  275. children: <Widget>[
  276. Text(
  277. "游戏步数",
  278. style: Theme.of(context).textTheme.subtitle1,
  279. ),
  280. Text("${_value == null ? ".." : "${_value.sum.stepGame}"}", style: Theme.of(context).textTheme.subtitle1)
  281. ],
  282. ),
  283. Space(
  284. height: 8,
  285. ),
  286. Row(
  287. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  288. children: <Widget>[
  289. Text(
  290. "游戏步频",
  291. style: Theme.of(context).textTheme.subtitle1,
  292. ),
  293. Text("${_value == null ? ".." : "${_value.sum.stepRate}"} /min",
  294. style: Theme.of(context).textTheme.subtitle1)
  295. ],
  296. ),
  297. Space(
  298. height: 8,
  299. ),
  300. Row(
  301. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  302. children: <Widget>[
  303. Text(
  304. "游戏最高步频",
  305. style: Theme.of(context).textTheme.subtitle1,
  306. ),
  307. Text("${_value == null ? ".." : "${_value.sum.stepRateMax}"} /min",
  308. style: Theme.of(context).textTheme.subtitle1)
  309. ],
  310. ),
  311. Space(
  312. height: 8,
  313. ),
  314. ],
  315. ),
  316. )
  317. ],
  318. ),
  319. ),
  320. );
  321. });
  322. },
  323. ),
  324. ),
  325. ],
  326. );
  327. },
  328. ),
  329. ));
  330. }
  331. int toType() {
  332. return TABS.indexOf(_tab.value);
  333. }
  334. void changeTab() async {
  335. _valueNotifierSportDetail.value = null;
  336. if (_valueNotifierDate.value == null) return;
  337. Future<RespData<SportStep>> data = createFuture(_valueNotifierDate.value);
  338. if (data != null) {
  339. data.then((value) => _valueNotifierSportDetail.value = value.data);
  340. }
  341. }
  342. Future<RespData<SportStep>> createQueryFuture(int offset) {
  343. int type = toType();
  344. DateTime next = offsetDate(type, offset);
  345. return createFuture(next);
  346. }
  347. Future<RespData<SportStep>> createFuture(DateTime time) {
  348. int type = toType();
  349. Future<RespData<SportStep>> data;
  350. switch (type) {
  351. case 0:
  352. data = api.getStepRecordListOneDay('${time.year}-${'${time.month}'.padLeft(2, '0')}-${'${time.day}'.padLeft(2, '0')}');
  353. break;
  354. case 1:
  355. DateTime start = DateTime(time.year, time.month, time.day - time.weekday + 1);
  356. DateTime end = DateTime(time.year, time.month, time.day + 6 - time.weekday + 1);
  357. data = api.getStepRecordListByDay('${start.year}-${'${start.month}'.padLeft(2, '0')}-${'${start.day}'.padLeft(2, '0')}',
  358. '${end.year}-${'${end.month}'.padLeft(2, '0')}-${'${end.day}'.padLeft(2, '0')}');
  359. break;
  360. case 2:
  361. DateTime start = DateTime(time.year, time.month, 1);
  362. DateTime end = DateTime(time.year, time.month + 1, 0);
  363. data = api.getStepRecordListByDay('${start.year}-${'${start.month}'.padLeft(2, '0')}-${'${start.day}'.padLeft(2, '0')}',
  364. '${end.year}-${'${end.month}'.padLeft(2, '0')}-${'${end.day}'.padLeft(2, '0')}');
  365. break;
  366. case 3:
  367. data = api.getStepRecordListByMonth(time.year);
  368. break;
  369. }
  370. return data;
  371. }
  372. void rollDate(int offset) {
  373. if (_valueNotifierSportDetail.value == null) return;
  374. int type = toType();
  375. DateTime next = offsetDate(type, offset);
  376. _valueNotifierDate.value = next;
  377. changeTab();
  378. }
  379. }