run_share_long.dart 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887
  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:amap_flutter_map/amap_flutter_map.dart';
  4. import 'package:flutter/foundation.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter/rendering.dart';
  7. import 'package:provider/provider.dart';
  8. import 'package:sport/bean/jog/detail.dart';
  9. import 'package:sport/pages/run/location.dart';
  10. import 'package:sport/pages/run/run_data.dart';
  11. import 'package:sport/pages/run/run_detail_page.dart';
  12. import 'package:sport/pages/run/run_page.dart';
  13. import 'package:sport/pages/run/statistics.dart';
  14. import 'package:sport/provider/user_model.dart';
  15. import 'package:sport/utils/DateFormat.dart';
  16. import 'package:sport/utils/sport_utils.dart';
  17. import 'package:sport/widgets/decoration.dart';
  18. import 'package:sport/widgets/image.dart';
  19. import 'package:sport/widgets/misc.dart';
  20. import 'package:sport/widgets/linear_progress_indicator.dart' as progress;
  21. import 'chart.dart';
  22. class RunShareLongPage extends StatefulWidget {
  23. final JogDetail detail;
  24. final File map;
  25. final GlobalKey repaintWidgetKey;
  26. const RunShareLongPage({Key? key, required this.detail, required this.map, required this.repaintWidgetKey}) : super(key: key);
  27. @override
  28. State<StatefulWidget> createState() => _PageState();
  29. }
  30. class _PageState extends State<RunShareLongPage> with AutomaticKeepAliveClientMixin {
  31. @override
  32. void initState() {
  33. super.initState();
  34. _showData();
  35. }
  36. _showData() {
  37. JogDetail data = widget.detail;
  38. totalDistance = data.distance?.toDouble() ?? 0;
  39. totalTime = data.duration ?? 0;
  40. totalConsume = data.consume ?? 0;
  41. totalStep = data.step ?? 0;
  42. timeStart = DateTime.parse(data.begin ?? "");
  43. timeEnd = DateTime.parse(data.end ?? "");
  44. met = data.met ?? 0;
  45. List<Location> points = data.points ?? [];
  46. if (data.stepInfo != null) stepRateList = json.decode(data.stepInfo ?? "[]").cast<int>();
  47. if (data.altitudeInfo != null) altitudeList = json.decode(data.altitudeInfo ?? "[]").cast<double>();
  48. var runData = RunData(0);
  49. if (points.length > 1) {
  50. runData.calPoints(points);
  51. }
  52. _pointsKm.addAll(runData.pointsKm);
  53. _pointsKmTime.addAll(runData.pointsKmTime);
  54. totalAltitude = runData.totalAltitude;
  55. marathonHalf = runData.marathonHalf;
  56. marathonAll = runData.marathonAll;
  57. if (stepRateList.isNotEmpty == true) stepMax = stepRateList.reduce((value, element) => value > element ? value : element);
  58. stepAvg = totalStep ~/ (totalTime / 60);
  59. if (_pointsKmTime.isNotEmpty == true) {
  60. kmTime = _pointsKmTime.values.fold(0, (value, element) => value + element.inSeconds);
  61. avg = kmTime ~/ _pointsKmTime.length;
  62. min = _pointsKmTime.values.reduce((value, element) => value.inSeconds < element.inSeconds ? value : element).inSeconds;
  63. max = _pointsKmTime.values.reduce((value, element) => value.inSeconds > element.inSeconds ? value : element).inSeconds;
  64. print("1111111111111111 $_pointsKmTime 22222222222222222 avg $avg $min");
  65. }
  66. if (points.isNotEmpty == true) {
  67. avgSpeed = points.map((e) => e.speed ?? 0).reduce((value, element) => value + element) / points.length;
  68. setState(() {
  69. this.points = points;
  70. });
  71. }
  72. }
  73. final Map<int, Location> _pointsKm = {};
  74. final Map<int, Duration> _pointsKmTime = {};
  75. final Map<String, Marker> _markerMap = <String, Marker>{};
  76. List<Location> points = <Location>[];
  77. List<int> stepRateList = [];
  78. List<double> altitudeList = [];
  79. double totalDistance = 0;
  80. double totalAltitude = 0;
  81. int totalTime = 0;
  82. int totalStep = 0;
  83. int stepAvg = 0;
  84. int stepMax = 0;
  85. int totalConsume = 0;
  86. double met = 0.0;
  87. int kmTime = 0;
  88. int avg = 0;
  89. double avgSpeed = 0.0;
  90. int min = 0;
  91. int max = 1;
  92. DateTime? timeStart;
  93. DateTime? timeEnd;
  94. int marathonHalf = 0;
  95. int marathonAll = 0;
  96. @override
  97. void dispose() {
  98. super.dispose();
  99. }
  100. Widget _box(String title, Widget child) {
  101. return Column(
  102. children: [
  103. SizedBox(
  104. height: 25,
  105. ),
  106. // Divider(
  107. // height: 40.0,
  108. // ),
  109. Padding(
  110. padding: const EdgeInsets.symmetric(horizontal: 24.0),
  111. child: Column(
  112. crossAxisAlignment: CrossAxisAlignment.start,
  113. children: [
  114. Row(
  115. children: [
  116. Text(
  117. "$title",
  118. style: Theme.of(context).textTheme.headline1!,
  119. ),
  120. const SizedBox(
  121. width: 6.0,
  122. ),
  123. Expanded(
  124. child: Image.asset("lib/assets/img/run_bg_title.png"),
  125. )
  126. ],
  127. ),
  128. const SizedBox(
  129. height: 16.0,
  130. ),
  131. child,
  132. ],
  133. ),
  134. ),
  135. ],
  136. );
  137. }
  138. @override
  139. Widget build(BuildContext context) {
  140. var maxKm = _pointsKmTime.isNotEmpty ? _pointsKmTime.keys.reduce((value, element) => value > element ? value : element) : 0;
  141. List<Widget> _kmList = [];
  142. if (_pointsKmTime.isNotEmpty) {
  143. List<int> keys = _pointsKmTime.keys.toList();
  144. int second = 0;
  145. int totalSecond = 0;
  146. for (var i = 0; i < keys.length; i++) {
  147. int e = keys[i];
  148. second += _pointsKmTime[e]?.inSeconds ?? 0;
  149. // print("11111111111111 --- $e $second");
  150. totalSecond += _pointsKmTime[e]?.inSeconds ?? 0;
  151. _kmList.add(Padding(
  152. padding: const EdgeInsets.symmetric(vertical: 1.0),
  153. child: Row(
  154. mainAxisSize: MainAxisSize.max,
  155. children: [
  156. ConstrainedBox(
  157. constraints: BoxConstraints(minWidth: 16),
  158. child: Padding(
  159. padding: const EdgeInsets.only(top: 1.0),
  160. child: Text(
  161. "${e.toString().padLeft(2, "0")}",
  162. style: Theme.of(context).textTheme.subtitle1!.copyWith(fontFamily: "DIN"),
  163. ),
  164. ),
  165. ),
  166. Expanded(
  167. child: Container(
  168. height: 12.0,
  169. color: Color(0xfff1f1f1),
  170. margin: const EdgeInsets.symmetric(horizontal: 10.0),
  171. child: CustomPaint(
  172. painter: progress.LinearProgressIndicator((_pointsKmTime[e]?.inSeconds ?? 0) == min ? [Color(0xffFFC57A), Color(0xffFF5B1D)] : [Color(0xffFFF4CE), Color(0xffFFC400)], (_pointsKmTime[e]?.inSeconds ?? 0) / max * 0.9),
  173. ),
  174. ),
  175. ),
  176. ConstrainedBox(
  177. constraints: BoxConstraints(minWidth: 50),
  178. child: Text(
  179. "${SportUtils.pace(SportUtils.calPace((_pointsKmTime[e]?.inSeconds ?? 0), 1))}",
  180. style: Theme.of(context).textTheme.subtitle1!,
  181. ),
  182. ),
  183. ],
  184. ),
  185. ));
  186. if ((i + 1) % 5 == 0) {
  187. String tips = "近5公里用时 ${DateFormat.toTime(second)}";
  188. if (i > 5) {
  189. tips += " ${i + 1}公里用时 ${DateFormat.toTime(totalSecond)}";
  190. }
  191. _kmList.add(Padding(
  192. padding: const EdgeInsets.fromLTRB(26, 5, 0, 15),
  193. child: Container(
  194. height: 18.0,
  195. child: Text(
  196. tips,
  197. maxLines: 1,
  198. style: Theme.of(context).textTheme.bodyText1?.copyWith(fontSize: 11),
  199. )),
  200. ));
  201. second = 0;
  202. }
  203. }
  204. }
  205. const _padding = 16.0;
  206. var header = Consumer<UserModel>(
  207. builder: (_, model, __) => Container(
  208. decoration: BoxDecoration(
  209. color: Colors.white,
  210. borderRadius: BorderRadius.only(topLeft: Radius.circular(10), topRight: Radius.circular(10)),
  211. ),
  212. child: Column(
  213. mainAxisSize: MainAxisSize.min,
  214. children: [
  215. Padding(
  216. padding: const EdgeInsets.symmetric(horizontal: _padding),
  217. child: Row(
  218. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  219. crossAxisAlignment: CrossAxisAlignment.end,
  220. children: [
  221. Column(
  222. mainAxisSize: MainAxisSize.min,
  223. crossAxisAlignment: CrossAxisAlignment.start,
  224. children: [
  225. Text(
  226. "趣动户外跑",
  227. style: Theme.of(context).textTheme.bodyText1!,
  228. ),
  229. const SizedBox(
  230. height: 45.0,
  231. ),
  232. Row(
  233. textBaseline: TextBaseline.alphabetic,
  234. mainAxisSize: MainAxisSize.min,
  235. children: [
  236. Text(
  237. "${formatNum((widget.detail.distance ?? 0) / 1000, 2)}",
  238. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 45.0, fontFamily: "DIN"),
  239. strutStyle: fixedLine,
  240. ),
  241. Text(" 公里", style: Theme.of(context).textTheme.subtitle1!),
  242. marathonAll > 0
  243. ? Container(
  244. decoration: BoxDecoration(borderRadius: BorderRadius.circular(3), color: COLOR_RUN_FAST),
  245. margin: const EdgeInsets.only(left: 10),
  246. padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
  247. child: Center(
  248. child: Text(
  249. "全马",
  250. style: Theme.of(context).textTheme.bodyText1!.copyWith(color: Colors.white),
  251. )),
  252. )
  253. : marathonHalf > 0
  254. ? Container(
  255. decoration: BoxDecoration(borderRadius: BorderRadius.circular(3), color: COLOR_RUN_SLOW),
  256. margin: const EdgeInsets.only(left: 10),
  257. padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
  258. child: Center(
  259. child: Text(
  260. "半马",
  261. style: Theme.of(context).textTheme.bodyText1!.copyWith(color: Colors.white),
  262. )),
  263. )
  264. : Container()
  265. ],
  266. ),
  267. ],
  268. ),
  269. Container(
  270. child: Column(
  271. crossAxisAlignment: CrossAxisAlignment.end,
  272. mainAxisSize: MainAxisSize.min,
  273. children: <Widget>[
  274. Container(
  275. decoration: BoxDecoration(color: Colors.grey, shape: BoxShape.circle),
  276. child: CircleAvatar(
  277. backgroundColor: Colors.black26,
  278. backgroundImage: userAvatarProvider(model.user.avatar),
  279. radius: 30.0,
  280. ),
  281. transform: Matrix4.translationValues(0, -24, 0),
  282. ),
  283. Text(model.user.name , style: Theme.of(context).textTheme.headline1!),
  284. const SizedBox(
  285. height: 12.0,
  286. ),
  287. Text(
  288. "${SportUtils.toDateTime(timeStart)} ${SportUtils.toEndTime(timeStart)} ~ ${SportUtils.toEndTime(timeEnd)}",
  289. style: Theme.of(context).textTheme.bodyText1!,
  290. )
  291. ],
  292. ),
  293. ),
  294. ],
  295. ),
  296. ),
  297. ],
  298. ),
  299. ),
  300. );
  301. var body = SingleChildScrollView(
  302. child: RepaintBoundary(
  303. key: widget.repaintWidgetKey,
  304. child: ClipRRect(
  305. borderRadius: BorderRadius.circular(10),
  306. child: Container(
  307. color: Colors.white,
  308. child: Column(
  309. children: [
  310. Container(
  311. width: double.infinity,
  312. child: Stack(
  313. children: [
  314. Padding(
  315. padding: const EdgeInsets.only(bottom: 50.0),
  316. child: Image.file(
  317. widget.map,
  318. fit: BoxFit.cover,
  319. height: MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top - 380 * MediaQuery.of(context).textScaleFactor,
  320. width: double.infinity,
  321. ),
  322. ),
  323. Positioned(left:12.0,right:12.0, bottom: 0, child: header,),
  324. Positioned(
  325. right: 12.0,
  326. top: 12.0,
  327. child: Container(
  328. padding: EdgeInsets.all(5.0),
  329. child: Row(
  330. children: [
  331. Image.asset(
  332. "lib/assets/img/logo_img_yellow_30.png",
  333. height: 30.0,
  334. width: 30.0,
  335. ),
  336. const SizedBox(
  337. width: 7,
  338. ),
  339. Text(
  340. // "已使用趣动运动",
  341. "趣动",
  342. style: Theme.of(context).textTheme.subtitle1!,
  343. ),
  344. // Text(
  345. // "${widget.detail.activeDay}",
  346. // style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Theme.of(context).accentColor),
  347. // ),
  348. // Text(
  349. // "天\t\t",
  350. // style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Colors.white),
  351. // ),
  352. ],
  353. ),
  354. )),
  355. ],
  356. ),
  357. ),
  358. Container(
  359. margin: const EdgeInsets.fromLTRB(12.0, 0, 12.0, 0),
  360. decoration: BoxDecoration(
  361. color: Colors.white,
  362. borderRadius: BorderRadius.only(bottomLeft: Radius.circular(10), bottomRight: Radius.circular(10)),
  363. ),
  364. child: Column(
  365. children: [
  366. Container(
  367. margin: const EdgeInsets.symmetric(vertical: 20.0),
  368. height: 20,
  369. child: Stack(
  370. fit: StackFit.expand,
  371. children: [
  372. Center(
  373. child: Container(
  374. height: 3,
  375. width: double.infinity,
  376. decoration: BoxDecoration(
  377. gradient: LinearGradient(
  378. begin: Alignment.centerLeft,
  379. end: Alignment.centerRight,
  380. colors: [
  381. COLOR_RUN_FAST,
  382. COLOR_RUN_MIDDLE,
  383. COLOR_RUN_SLOW,
  384. ],
  385. ),
  386. ),
  387. ),
  388. ),
  389. if (min != 0)
  390. Positioned(
  391. top: 0,
  392. bottom: 0,
  393. left: _padding,
  394. child: Container(
  395. color: Colors.white,
  396. height: double.infinity,
  397. padding: const EdgeInsets.symmetric(horizontal: 4.0),
  398. child: Center(
  399. child: Text(
  400. // "${SportUtils.toEndTime(timeStart)}开始",
  401. "快 ${SportUtils.pace(SportUtils.calPace((min), 1))}",
  402. style: Theme.of(context).textTheme.bodyText1!,
  403. ),
  404. ),
  405. ),
  406. ),
  407. if (max != 0)
  408. Positioned(
  409. top: 0,
  410. bottom: 0,
  411. right: _padding,
  412. child: Container(
  413. color: Colors.white,
  414. height: double.infinity,
  415. padding: const EdgeInsets.symmetric(horizontal: 4.0),
  416. child: Center(
  417. child: Text(
  418. // "${SportUtils.toEndTime(timeEnd)}结束",
  419. "慢 ${SportUtils.pace(SportUtils.calPace((max), 1))}",
  420. style: Theme.of(context).textTheme.bodyText1!,
  421. ),
  422. ),
  423. ),
  424. ),
  425. ],
  426. ),
  427. ),
  428. Padding(
  429. padding: const EdgeInsets.only(left: 20.0),
  430. child: Row(
  431. crossAxisAlignment: CrossAxisAlignment.end,
  432. children: [
  433. Expanded(
  434. flex: 4,
  435. child: Column(
  436. crossAxisAlignment: CrossAxisAlignment.start,
  437. children: [
  438. Text(
  439. "${DateFormat.toTime(totalTime)}",
  440. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"),
  441. ),
  442. Text(
  443. "时长",
  444. style: Theme.of(context).textTheme.bodyText1!,
  445. )
  446. ],
  447. ),
  448. ),
  449. Expanded(
  450. flex: 3,
  451. child: Column(
  452. crossAxisAlignment: CrossAxisAlignment.start,
  453. children: [
  454. Text(
  455. "${met.toStringAsFixed(1)}",
  456. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"),
  457. ),
  458. Text(
  459. "MET值",
  460. style: Theme.of(context).textTheme.bodyText1!,
  461. )
  462. ],
  463. ),
  464. ),
  465. Expanded(
  466. flex: 4,
  467. child: Column(
  468. crossAxisAlignment: CrossAxisAlignment.start,
  469. children: [
  470. Row(
  471. children: [
  472. Text(
  473. "${SportUtils.pace11(SportUtils.calPace(totalTime, totalDistance / 1000))}",
  474. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"),
  475. ),
  476. Text(
  477. "′",
  478. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0),
  479. ),
  480. Text(
  481. "${SportUtils.pace12(SportUtils.calPace(totalTime, totalDistance / 1000))}",
  482. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"),
  483. ),
  484. Text(
  485. "″",
  486. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0),
  487. ),
  488. ],
  489. ),
  490. Text(
  491. "配速(每公里用时)",
  492. style: Theme.of(context).textTheme.bodyText1!,
  493. )
  494. ],
  495. ),
  496. ),
  497. ],
  498. ),
  499. ),
  500. const SizedBox(
  501. height: 20.0,
  502. ),
  503. Padding(
  504. padding: const EdgeInsets.only(left: 20.0),
  505. child: Row(
  506. children: [
  507. Expanded(
  508. flex: 4,
  509. child: Column(
  510. crossAxisAlignment: CrossAxisAlignment.start,
  511. children: [
  512. Text(
  513. "$totalConsume",
  514. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"),
  515. ),
  516. Text(
  517. "消耗(大卡)",
  518. style: Theme.of(context).textTheme.bodyText1!,
  519. )
  520. ],
  521. ),
  522. ),
  523. Expanded(
  524. flex: 3,
  525. child: Column(
  526. crossAxisAlignment: CrossAxisAlignment.start,
  527. children: [
  528. Text(
  529. totalStep == 0 ? "0" : "${totalStep ~/ (totalTime / 60)}",
  530. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"),
  531. ),
  532. Text(
  533. "步频(步/分钟)",
  534. style: Theme.of(context).textTheme.bodyText1!,
  535. ),
  536. ],
  537. ),
  538. ),
  539. Expanded(
  540. flex: 4,
  541. child: Column(
  542. crossAxisAlignment: CrossAxisAlignment.start,
  543. children: [
  544. Text(
  545. "${totalTime == 0 ? 0 : (totalDistance / 1000.0 / totalTime * 3600).toStringAsFixed(1)}",
  546. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"),
  547. ),
  548. Text(
  549. "时速(公里/小时)",
  550. style: Theme.of(context).textTheme.bodyText1!,
  551. )
  552. ],
  553. ),
  554. ),
  555. ],
  556. ),
  557. ),
  558. const SizedBox(
  559. height: 20.0,
  560. ),
  561. Center(
  562. child: Container(
  563. decoration: BoxDecoration(color: Theme.of(context).backgroundColor, borderRadius: BorderRadius.circular(radius)),
  564. margin: EdgeInsets.symmetric(vertical: 10.0),
  565. padding: EdgeInsets.fromLTRB(30.0, 2, 30.0, 0),
  566. child: Row(
  567. mainAxisSize: MainAxisSize.min,
  568. children: [
  569. Image.asset(
  570. "lib/assets/img/run_icon_today.png",
  571. color: Color(0xffC5C5C5),
  572. ),
  573. SizedBox(
  574. width: 12.0,
  575. ),
  576. Row(
  577. children: [
  578. Text(
  579. "步幅 ",
  580. style: Theme.of(context).textTheme.subtitle2!,
  581. ),
  582. Text(
  583. totalStep == 0 ? "0" : "${(totalDistance / totalStep * 100).toInt()}",
  584. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"),
  585. ),
  586. Text(
  587. " 厘米",
  588. style: Theme.of(context).textTheme.bodyText1!,
  589. ),
  590. ],
  591. ),
  592. Container(
  593. width: 1,
  594. height: 20.0,
  595. color: Color(0xffdcdcdc),
  596. margin: EdgeInsets.symmetric(horizontal: 20.0),
  597. ),
  598. Row(
  599. children: [
  600. Text(
  601. "步数 ",
  602. style: Theme.of(context).textTheme.subtitle2!,
  603. ),
  604. Text(
  605. "${totalStep}",
  606. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"),
  607. ),
  608. Text(
  609. " 步",
  610. style: Theme.of(context).textTheme.bodyText1!,
  611. ),
  612. ],
  613. ),
  614. ],
  615. ),
  616. ),
  617. ),
  618. ],
  619. ),
  620. ),
  621. const SizedBox(
  622. height: 12.0,
  623. ),
  624. BoxWidget(
  625. title: "配速",
  626. icon: "run_icon_tile1.png",
  627. child: Column(
  628. mainAxisSize: MainAxisSize.min,
  629. children: [
  630. if (marathonHalf > 0)
  631. Align(
  632. alignment: Alignment.centerLeft,
  633. child: Padding(
  634. padding: const EdgeInsets.only(bottom: 16.0, top: 8.0),
  635. child: Column(
  636. crossAxisAlignment: CrossAxisAlignment.start,
  637. children: [
  638. Text(
  639. "${DateFormat.toTime(marathonHalf)}",
  640. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 25.0, fontFamily: "DIN"),
  641. ),
  642. Text(
  643. "半马总用时",
  644. style: Theme.of(context).textTheme.bodyText1!,
  645. )
  646. ],
  647. mainAxisSize: MainAxisSize.min,
  648. ),
  649. ),
  650. ),
  651. if (marathonAll > 0)
  652. Align(
  653. alignment: Alignment.centerLeft,
  654. child: Padding(
  655. padding: const EdgeInsets.only(bottom: 16.0, top: 8.0),
  656. child: Column(
  657. crossAxisAlignment: CrossAxisAlignment.start,
  658. children: [
  659. Text(
  660. "${DateFormat.toTime(marathonHalf)}",
  661. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 25.0, fontFamily: "DIN"),
  662. ),
  663. Text(
  664. "全马总用时",
  665. style: Theme.of(context).textTheme.bodyText1!,
  666. )
  667. ],
  668. mainAxisSize: MainAxisSize.min,
  669. ),
  670. ),
  671. ),
  672. CustomPaint(
  673. foregroundPainter: AvgPainter(context, avg * 1.0 / max * 0.9, avg, _pointsKmTime.length, MediaQuery.of(context).textScaleFactor),
  674. child: Column(
  675. crossAxisAlignment: CrossAxisAlignment.start,
  676. children: [
  677. Row(
  678. mainAxisSize: MainAxisSize.max,
  679. crossAxisAlignment: CrossAxisAlignment.start,
  680. children: [
  681. Text(
  682. "公里",
  683. style: Theme.of(context).textTheme.bodyText1!,
  684. ),
  685. Expanded(
  686. child: Container(height: 18,),
  687. ),
  688. Text(
  689. "配速",
  690. style: Theme.of(context).textTheme.bodyText1!,
  691. ),
  692. const SizedBox(
  693. width: 20.0,
  694. ),
  695. ],
  696. ),
  697. const SizedBox(
  698. height: 12.0,
  699. ),
  700. Column(
  701. crossAxisAlignment: CrossAxisAlignment.start,
  702. children: _kmList,
  703. ),
  704. // if (_pointsKmTime.length > 0 && totalTime > 0)
  705. // Row(
  706. // mainAxisAlignment: MainAxisAlignment.spaceAround,
  707. // children: [
  708. // RichText(
  709. // text: TextSpan(children: [TextSpan(text: "$maxKm公里累计用时 ", style: Theme.of(context).textTheme.bodyText1!), TextSpan(text: "${DateFormat.toTime(kmTime)}", style: Theme.of(context).textTheme.bodyText1!.copyWith(color: Theme.of(context).accentColor))]),
  710. // ),
  711. // if (totalTime - kmTime > 0)
  712. // RichText(
  713. // text: TextSpan(children: [TextSpan(text: "最后不足1公里用时 ", style: Theme.of(context).textTheme.bodyText1!), TextSpan(text: "${DateFormat.toTime((totalTime) - kmTime)}", style: Theme.of(context).textTheme.bodyText1!.copyWith(color: Theme.of(context).accentColor))]),
  714. // ),
  715. // ],
  716. // ),
  717. if (_pointsKmTime.length > 0 && totalTime > 0)
  718. Padding(
  719. padding: const EdgeInsets.fromLTRB(26, 5, 0, 15),
  720. child: Container(
  721. height: 18.0,
  722. child: Text(
  723. "最后不足1公里用时 ${DateFormat.toTime((totalTime) - kmTime)}",
  724. style: Theme.of(context).textTheme.bodyText1?.copyWith(fontSize: 11),
  725. )),
  726. ),
  727. const SizedBox(
  728. height: 16.0,
  729. ),
  730. ],
  731. ),
  732. ),
  733. ],
  734. )),
  735. BoxWidget(
  736. title: "步频",
  737. icon: "run_icon_tile2.png",
  738. child: Column(
  739. children: [
  740. Row(
  741. children: [
  742. Column(
  743. crossAxisAlignment: CrossAxisAlignment.start,
  744. children: [
  745. Text(
  746. "$stepAvg",
  747. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 25.0, fontFamily: "DIN"),
  748. ),
  749. Text(
  750. "平均步频(步/分钟)",
  751. style: Theme.of(context).textTheme.bodyText1!,
  752. )
  753. ],
  754. ),
  755. const SizedBox(
  756. width: 37.0,
  757. ),
  758. Column(
  759. crossAxisAlignment: CrossAxisAlignment.start,
  760. children: [
  761. Text(
  762. "$stepMax",
  763. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 25.0, fontFamily: "DIN"),
  764. ),
  765. Text(
  766. "最高步频(步/分钟)",
  767. style: Theme.of(context).textTheme.bodyText1!,
  768. )
  769. ],
  770. ),
  771. ],
  772. ),
  773. RunChart(
  774. values: stepRateList.map((e) => e.toDouble()).toList(),
  775. gradient: false,
  776. ),
  777. ],
  778. )),
  779. BoxWidget(
  780. title: "运动海拔",
  781. icon: "run_icon_tile3.png",
  782. child: Column(
  783. children: [
  784. Row(
  785. children: [
  786. Column(
  787. crossAxisAlignment: CrossAxisAlignment.start,
  788. children: [
  789. Text(
  790. "${(totalAltitude).toStringAsFixed(1)}",
  791. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 25.0, fontFamily: "DIN"),
  792. ),
  793. Text(
  794. "累计爬升(米)",
  795. style: Theme.of(context).textTheme.bodyText1!,
  796. )
  797. ],
  798. ),
  799. ],
  800. ),
  801. RunChart(
  802. values: altitudeList.map((e) => e.toDouble()).toList(),
  803. // values: [-10,20,0,0,1,2,3,4,5,6,8,9,8,10,-6,-5,-20, 20, 30],
  804. gradient: true,
  805. ),
  806. ],
  807. )),
  808. ],
  809. ),
  810. ),
  811. ),
  812. ));
  813. return Stack(
  814. children: [
  815. Padding(
  816. padding: const EdgeInsets.only(bottom: 50.0),
  817. child: body,
  818. ),
  819. IgnorePointer(
  820. child: Align(
  821. alignment: Alignment.bottomCenter,
  822. child: Container(
  823. height: 150.0,
  824. decoration: BoxDecoration(
  825. gradient: LinearGradient(
  826. begin: Alignment.topCenter,
  827. end: Alignment.bottomCenter,
  828. colors: [
  829. Colors.white.withOpacity(.0),
  830. Colors.white,
  831. ],
  832. )),
  833. child: Align(
  834. alignment: Alignment.bottomCenter,
  835. child: Row(
  836. mainAxisSize: MainAxisSize.min,
  837. crossAxisAlignment: CrossAxisAlignment.center,
  838. children: [
  839. Text(
  840. "上拉看全部",
  841. style: Theme.of(context).textTheme.bodyText1!,
  842. ),
  843. const SizedBox(
  844. width: 5,
  845. ),
  846. arrowTop()
  847. ],
  848. ),
  849. ),
  850. ),
  851. ),
  852. ),
  853. ],
  854. );
  855. }
  856. @override
  857. bool get wantKeepAlive => true;
  858. }