step_page.dart 21 KB

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