import 'dart:convert'; import 'dart:io'; import 'package:amap_flutter_map/amap_flutter_map.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:provider/provider.dart'; import 'package:sport/bean/jog/detail.dart'; import 'package:sport/pages/run/location.dart'; import 'package:sport/pages/run/run_data.dart'; import 'package:sport/pages/run/run_detail_page.dart'; import 'package:sport/pages/run/run_page.dart'; import 'package:sport/pages/run/statistics.dart'; import 'package:sport/provider/user_model.dart'; import 'package:sport/utils/DateFormat.dart'; import 'package:sport/utils/sport_utils.dart'; import 'package:sport/widgets/decoration.dart'; import 'package:sport/widgets/image.dart'; import 'package:sport/widgets/misc.dart'; import 'package:sport/widgets/linear_progress_indicator.dart' as progress; import 'chart.dart'; class RunShareLongPage extends StatefulWidget { final JogDetail detail; final File map; final GlobalKey repaintWidgetKey; const RunShareLongPage({Key? key, required this.detail, required this.map, required this.repaintWidgetKey}) : super(key: key); @override State createState() => _PageState(); } class _PageState extends State with AutomaticKeepAliveClientMixin { @override void initState() { super.initState(); _showData(); } _showData() { JogDetail data = widget.detail; totalDistance = data.distance?.toDouble() ?? 0; totalTime = data.duration ?? 0; totalConsume = data.consume ?? 0; totalStep = data.step ?? 0; timeStart = DateTime.parse(data.begin ?? ""); timeEnd = DateTime.parse(data.end ?? ""); met = data.met ?? 0; List points = data.points ?? []; if (data.stepInfo != null) stepRateList = json.decode(data.stepInfo ?? "[]").cast(); if (data.altitudeInfo != null) altitudeList = json.decode(data.altitudeInfo ?? "[]").cast(); var runData = RunData(0); if (points.length > 1) { runData.calPoints(points); } _pointsKm.addAll(runData.pointsKm); _pointsKmTime.addAll(runData.pointsKmTime); totalAltitude = runData.totalAltitude; marathonHalf = runData.marathonHalf; marathonAll = runData.marathonAll; if (stepRateList.isNotEmpty == true) stepMax = stepRateList.reduce((value, element) => value > element ? value : element); stepAvg = totalStep ~/ (totalTime / 60); if (_pointsKmTime.isNotEmpty == true) { kmTime = _pointsKmTime.values.fold(0, (value, element) => value + element.inSeconds); avg = kmTime ~/ _pointsKmTime.length; min = _pointsKmTime.values.reduce((value, element) => value.inSeconds < element.inSeconds ? value : element).inSeconds; max = _pointsKmTime.values.reduce((value, element) => value.inSeconds > element.inSeconds ? value : element).inSeconds; print("1111111111111111 $_pointsKmTime 22222222222222222 avg $avg $min"); } if (points.isNotEmpty == true) { avgSpeed = points.map((e) => e.speed ?? 0).reduce((value, element) => value + element) / points.length; setState(() { this.points = points; }); } } final Map _pointsKm = {}; final Map _pointsKmTime = {}; final Map _markerMap = {}; List points = []; List stepRateList = []; List altitudeList = []; double totalDistance = 0; double totalAltitude = 0; int totalTime = 0; int totalStep = 0; int stepAvg = 0; int stepMax = 0; int totalConsume = 0; double met = 0.0; int kmTime = 0; int avg = 0; double avgSpeed = 0.0; int min = 0; int max = 1; DateTime? timeStart; DateTime? timeEnd; int marathonHalf = 0; int marathonAll = 0; @override void dispose() { super.dispose(); } Widget _box(String title, Widget child) { return Column( children: [ SizedBox( height: 25, ), // Divider( // height: 40.0, // ), Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( "$title", style: Theme.of(context).textTheme.headline1!, ), const SizedBox( width: 6.0, ), Expanded( child: Image.asset("lib/assets/img/run_bg_title.png"), ) ], ), const SizedBox( height: 16.0, ), child, ], ), ), ], ); } @override Widget build(BuildContext context) { var maxKm = _pointsKmTime.isNotEmpty ? _pointsKmTime.keys.reduce((value, element) => value > element ? value : element) : 0; List _kmList = []; if (_pointsKmTime.isNotEmpty) { List keys = _pointsKmTime.keys.toList(); int second = 0; int totalSecond = 0; for (var i = 0; i < keys.length; i++) { int e = keys[i]; second += _pointsKmTime[e]?.inSeconds ?? 0; // print("11111111111111 --- $e $second"); totalSecond += _pointsKmTime[e]?.inSeconds ?? 0; _kmList.add(Padding( padding: const EdgeInsets.symmetric(vertical: 1.0), child: Row( mainAxisSize: MainAxisSize.max, children: [ ConstrainedBox( constraints: BoxConstraints(minWidth: 16), child: Padding( padding: const EdgeInsets.only(top: 1.0), child: Text( "${e.toString().padLeft(2, "0")}", style: Theme.of(context).textTheme.subtitle1!.copyWith(fontFamily: "DIN"), ), ), ), Expanded( child: Container( height: 12.0, color: Color(0xfff1f1f1), margin: const EdgeInsets.symmetric(horizontal: 10.0), child: CustomPaint( painter: progress.LinearProgressIndicator((_pointsKmTime[e]?.inSeconds ?? 0) == min ? [Color(0xffFFC57A), Color(0xffFF5B1D)] : [Color(0xffFFF4CE), Color(0xffFFC400)], (_pointsKmTime[e]?.inSeconds ?? 0) / max * 0.9), ), ), ), ConstrainedBox( constraints: BoxConstraints(minWidth: 50), child: Text( "${SportUtils.pace(SportUtils.calPace((_pointsKmTime[e]?.inSeconds ?? 0), 1))}", style: Theme.of(context).textTheme.subtitle1!, ), ), ], ), )); if ((i + 1) % 5 == 0) { String tips = "近5公里用时 ${DateFormat.toTime(second)}"; if (i > 5) { tips += " ${i + 1}公里用时 ${DateFormat.toTime(totalSecond)}"; } _kmList.add(Padding( padding: const EdgeInsets.fromLTRB(26, 5, 0, 15), child: Container( height: 18.0, child: Text( tips, maxLines: 1, style: Theme.of(context).textTheme.bodyText1?.copyWith(fontSize: 11), )), )); second = 0; } } } const _padding = 16.0; var header = Consumer( builder: (_, model, __) => Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only(topLeft: Radius.circular(10), topRight: Radius.circular(10)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: _padding), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end, children: [ Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "趣动户外跑", style: Theme.of(context).textTheme.bodyText1!, ), const SizedBox( height: 45.0, ), Row( textBaseline: TextBaseline.alphabetic, mainAxisSize: MainAxisSize.min, children: [ Text( "${formatNum((widget.detail.distance ?? 0) / 1000, 2)}", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 45.0, fontFamily: "DIN"), strutStyle: fixedLine, ), Text(" 公里", style: Theme.of(context).textTheme.subtitle1!), marathonAll > 0 ? Container( decoration: BoxDecoration(borderRadius: BorderRadius.circular(3), color: COLOR_RUN_FAST), margin: const EdgeInsets.only(left: 10), padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), child: Center( child: Text( "全马", style: Theme.of(context).textTheme.bodyText1!.copyWith(color: Colors.white), )), ) : marathonHalf > 0 ? Container( decoration: BoxDecoration(borderRadius: BorderRadius.circular(3), color: COLOR_RUN_SLOW), margin: const EdgeInsets.only(left: 10), padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), child: Center( child: Text( "半马", style: Theme.of(context).textTheme.bodyText1!.copyWith(color: Colors.white), )), ) : Container() ], ), ], ), Container( child: Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: [ Container( decoration: BoxDecoration(color: Colors.grey, shape: BoxShape.circle), child: CircleAvatar( backgroundColor: Colors.black26, backgroundImage: userAvatarProvider(model.user.avatar), radius: 30.0, ), transform: Matrix4.translationValues(0, -24, 0), ), Text(model.user.name , style: Theme.of(context).textTheme.headline1!), const SizedBox( height: 12.0, ), Text( "${SportUtils.toDateTime(timeStart)} ${SportUtils.toEndTime(timeStart)} ~ ${SportUtils.toEndTime(timeEnd)}", style: Theme.of(context).textTheme.bodyText1!, ) ], ), ), ], ), ), ], ), ), ); var body = SingleChildScrollView( child: RepaintBoundary( key: widget.repaintWidgetKey, child: ClipRRect( borderRadius: BorderRadius.circular(10), child: Container( color: Colors.white, child: Column( children: [ Container( width: double.infinity, child: Stack( children: [ Padding( padding: const EdgeInsets.only(bottom: 50.0), child: Image.file( widget.map, fit: BoxFit.cover, height: MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top - 380 * MediaQuery.of(context).textScaleFactor, width: double.infinity, ), ), Positioned(left:12.0,right:12.0, bottom: 0, child: header,), Positioned( right: 12.0, top: 12.0, child: Container( padding: EdgeInsets.all(5.0), child: Row( children: [ Image.asset( "lib/assets/img/logo_img_yellow_30.png", height: 30.0, width: 30.0, ), const SizedBox( width: 7, ), Text( // "已使用趣动运动", "趣动", style: Theme.of(context).textTheme.subtitle1!, ), // Text( // "${widget.detail.activeDay}", // style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Theme.of(context).accentColor), // ), // Text( // "天\t\t", // style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Colors.white), // ), ], ), )), ], ), ), Container( margin: const EdgeInsets.fromLTRB(12.0, 0, 12.0, 0), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only(bottomLeft: Radius.circular(10), bottomRight: Radius.circular(10)), ), child: Column( children: [ Container( margin: const EdgeInsets.symmetric(vertical: 20.0), height: 20, child: Stack( fit: StackFit.expand, children: [ Center( child: Container( height: 3, width: double.infinity, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ COLOR_RUN_FAST, COLOR_RUN_MIDDLE, COLOR_RUN_SLOW, ], ), ), ), ), if (min != 0) Positioned( top: 0, bottom: 0, left: _padding, child: Container( color: Colors.white, height: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 4.0), child: Center( child: Text( // "${SportUtils.toEndTime(timeStart)}开始", "快 ${SportUtils.pace(SportUtils.calPace((min), 1))}", style: Theme.of(context).textTheme.bodyText1!, ), ), ), ), if (max != 0) Positioned( top: 0, bottom: 0, right: _padding, child: Container( color: Colors.white, height: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 4.0), child: Center( child: Text( // "${SportUtils.toEndTime(timeEnd)}结束", "慢 ${SportUtils.pace(SportUtils.calPace((max), 1))}", style: Theme.of(context).textTheme.bodyText1!, ), ), ), ), ], ), ), Padding( padding: const EdgeInsets.only(left: 20.0), child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded( flex: 4, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${DateFormat.toTime(totalTime)}", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"), ), Text( "时长", style: Theme.of(context).textTheme.bodyText1!, ) ], ), ), Expanded( flex: 3, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${met.toStringAsFixed(1)}", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"), ), Text( "MET值", style: Theme.of(context).textTheme.bodyText1!, ) ], ), ), Expanded( flex: 4, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( "${SportUtils.pace11(SportUtils.calPace(totalTime, totalDistance / 1000))}", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"), ), Text( "′", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0), ), Text( "${SportUtils.pace12(SportUtils.calPace(totalTime, totalDistance / 1000))}", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"), ), Text( "″", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0), ), ], ), Text( "配速(每公里用时)", style: Theme.of(context).textTheme.bodyText1!, ) ], ), ), ], ), ), const SizedBox( height: 20.0, ), Padding( padding: const EdgeInsets.only(left: 20.0), child: Row( children: [ Expanded( flex: 4, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "$totalConsume", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"), ), Text( "消耗(大卡)", style: Theme.of(context).textTheme.bodyText1!, ) ], ), ), Expanded( flex: 3, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( totalStep == 0 ? "0" : "${totalStep ~/ (totalTime / 60)}", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"), ), Text( "步频(步/分钟)", style: Theme.of(context).textTheme.bodyText1!, ), ], ), ), Expanded( flex: 4, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${totalTime == 0 ? 0 : (totalDistance / 1000.0 / totalTime * 3600).toStringAsFixed(1)}", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"), ), Text( "时速(公里/小时)", style: Theme.of(context).textTheme.bodyText1!, ) ], ), ), ], ), ), const SizedBox( height: 20.0, ), Center( child: Container( decoration: BoxDecoration(color: Theme.of(context).backgroundColor, borderRadius: BorderRadius.circular(radius)), margin: EdgeInsets.symmetric(vertical: 10.0), padding: EdgeInsets.fromLTRB(30.0, 2, 30.0, 0), child: Row( mainAxisSize: MainAxisSize.min, children: [ Image.asset( "lib/assets/img/run_icon_today.png", color: Color(0xffC5C5C5), ), SizedBox( width: 12.0, ), Row( children: [ Text( "步幅 ", style: Theme.of(context).textTheme.subtitle2!, ), Text( totalStep == 0 ? "0" : "${(totalDistance / totalStep * 100).toInt()}", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"), ), Text( " 厘米", style: Theme.of(context).textTheme.bodyText1!, ), ], ), Container( width: 1, height: 20.0, color: Color(0xffdcdcdc), margin: EdgeInsets.symmetric(horizontal: 20.0), ), Row( children: [ Text( "步数 ", style: Theme.of(context).textTheme.subtitle2!, ), Text( "${totalStep}", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 22.0, fontFamily: "DIN"), ), Text( " 步", style: Theme.of(context).textTheme.bodyText1!, ), ], ), ], ), ), ), ], ), ), const SizedBox( height: 12.0, ), BoxWidget( title: "配速", icon: "run_icon_tile1.png", child: Column( mainAxisSize: MainAxisSize.min, children: [ if (marathonHalf > 0) Align( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.only(bottom: 16.0, top: 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${DateFormat.toTime(marathonHalf)}", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 25.0, fontFamily: "DIN"), ), Text( "半马总用时", style: Theme.of(context).textTheme.bodyText1!, ) ], mainAxisSize: MainAxisSize.min, ), ), ), if (marathonAll > 0) Align( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.only(bottom: 16.0, top: 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${DateFormat.toTime(marathonHalf)}", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 25.0, fontFamily: "DIN"), ), Text( "全马总用时", style: Theme.of(context).textTheme.bodyText1!, ) ], mainAxisSize: MainAxisSize.min, ), ), ), CustomPaint( foregroundPainter: AvgPainter(context, avg * 1.0 / max * 0.9, avg, _pointsKmTime.length, MediaQuery.of(context).textScaleFactor), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "公里", style: Theme.of(context).textTheme.bodyText1!, ), Expanded( child: Container(height: 18,), ), Text( "配速", style: Theme.of(context).textTheme.bodyText1!, ), const SizedBox( width: 20.0, ), ], ), const SizedBox( height: 12.0, ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: _kmList, ), // if (_pointsKmTime.length > 0 && totalTime > 0) // Row( // mainAxisAlignment: MainAxisAlignment.spaceAround, // children: [ // RichText( // 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))]), // ), // if (totalTime - kmTime > 0) // RichText( // 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))]), // ), // ], // ), if (_pointsKmTime.length > 0 && totalTime > 0) Padding( padding: const EdgeInsets.fromLTRB(26, 5, 0, 15), child: Container( height: 18.0, child: Text( "最后不足1公里用时 ${DateFormat.toTime((totalTime) - kmTime)}", style: Theme.of(context).textTheme.bodyText1?.copyWith(fontSize: 11), )), ), const SizedBox( height: 16.0, ), ], ), ), ], )), BoxWidget( title: "步频", icon: "run_icon_tile2.png", child: Column( children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "$stepAvg", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 25.0, fontFamily: "DIN"), ), Text( "平均步频(步/分钟)", style: Theme.of(context).textTheme.bodyText1!, ) ], ), const SizedBox( width: 37.0, ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "$stepMax", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 25.0, fontFamily: "DIN"), ), Text( "最高步频(步/分钟)", style: Theme.of(context).textTheme.bodyText1!, ) ], ), ], ), RunChart( values: stepRateList.map((e) => e.toDouble()).toList(), gradient: false, ), ], )), BoxWidget( title: "运动海拔", icon: "run_icon_tile3.png", child: Column( children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${(totalAltitude).toStringAsFixed(1)}", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 25.0, fontFamily: "DIN"), ), Text( "累计爬升(米)", style: Theme.of(context).textTheme.bodyText1!, ) ], ), ], ), RunChart( values: altitudeList.map((e) => e.toDouble()).toList(), // values: [-10,20,0,0,1,2,3,4,5,6,8,9,8,10,-6,-5,-20, 20, 30], gradient: true, ), ], )), ], ), ), ), )); return Stack( children: [ Padding( padding: const EdgeInsets.only(bottom: 50.0), child: body, ), IgnorePointer( child: Align( alignment: Alignment.bottomCenter, child: Container( height: 150.0, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.white.withOpacity(.0), Colors.white, ], )), child: Align( alignment: Alignment.bottomCenter, child: Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "上拉看全部", style: Theme.of(context).textTheme.bodyText1!, ), const SizedBox( width: 5, ), arrowTop() ], ), ), ), ), ), ], ); } @override bool get wantKeepAlive => true; }