import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:get_it/get_it.dart'; import 'package:provider/provider.dart'; import 'package:sport/bean/sport_detail.dart'; import 'package:sport/bean/sport_index.dart'; import 'package:sport/pages/data/sport_data_page.dart'; import 'package:sport/pages/data/sport_reference_page.dart'; import 'package:sport/pages/home/duration_setting_page.dart'; import 'package:sport/pages/home/strength_page.dart'; import 'package:sport/pages/run/statistics_page.dart'; import 'package:sport/provider/bluetooth.dart'; import 'package:sport/provider/user_model.dart'; import 'package:sport/router/navigator_util.dart'; import 'package:sport/services/api/inject_api.dart'; import 'package:sport/services/api/resp.dart'; import 'package:sport/utils/sport_utils.dart'; import 'package:sport/widgets/chart.dart'; import 'package:sport/widgets/circular_percent_indicator.dart'; import 'package:sport/widgets/data_chart.dart'; import 'package:sport/widgets/decoration.dart'; import 'package:sport/widgets/image.dart'; import 'package:sport/widgets/loading.dart'; import 'package:umeng_common_sdk/umeng_common_sdk.dart'; class SportDataDetailPage extends StatefulWidget { final int type; final int sportType; final DateTime time; final DateTime? selectDate; final int index; const SportDataDetailPage({Key? key, required this.type, required this.sportType, required this.time, this.selectDate, required this.index}) : super(key: key); @override State createState() => SportDataDetailPageState(); } class SportDataDetailPageState extends State with InjectApi, AutomaticKeepAliveClientMixin { int _tabIndex = 0; int _index = -1; int _initialPage = 0; SportDetail? _detail; final List> tabs = [ {"icon": "icon_table_consume_default", "icon_select": "icon_table_consume_select", "name": "消耗", "color": 0xffFFC400}, {"icon": "icon_table_steps_default", "icon_select": "icon_table_steps_select", "name": "步数", "color": 0xff27D171}, {"icon": "icon_table_duration_default", "icon_select": "icon_table_duration_select", "name": "时长", "color": 0xff5498FF}, {"icon": "icon_table_strength_default", "icon_select": "icon_table_strength_select", "name": "强度", "color": 0xffFF5B1D}, ]; @override void initState() { super.initState(); _tabIndex = widget.sportType; _loadData(); } _loadData() async { setState(() { _detail = null; }); var bluetooth = GetIt.I(); if (bluetooth.isConnected == true) { await bluetooth.queryDeviceStep(); } createFuture(widget.time).then((value) { if (mounted) setState(() { _detail = value; }); }); } Text title() { int type = widget.type; DateTime now = DateTime.now(); DateTime time = widget.time; if (type == 0) { return Text( "${time.year} ${'${time.month}'.padLeft(2, '0')}-${'${time.day}'.padLeft(2, '0')} ${WEEK[time.weekday == 0 ? 6 : time.weekday - 1]}${now.day == time.day ? "(今天)" : ""}", style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Colors.white), ); } else if (type == 1) { DateTime start = DateTime(time.year, time.month, time.day - time.weekday + 1); DateTime end = DateTime(time.year, time.month, time.day + 6 - time.weekday + 1); return Text( "${start.year} ${'${start.month}'.padLeft(2, '0')}-${'${start.day}'.padLeft(2, '0')} 至 ${start.year == end.year ? "" : "${end.year} "}${'${end.month}'.padLeft(2, '0')}-${'${end.day}'.padLeft(2, '0')}${now.day >= time.day && now.day < end.day ? "(本周)" : ""}", style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Colors.white), ); } else if (type == 2) { return Text( "${time.year} ${'${time.month}'.padLeft(2, '0')}月${now.month == time.month ? "(本月)" : ""}", style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Colors.white), ); } else if (type == 3) { return Text( "${time.year}年", style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Colors.white), ); } return Text(""); } Future createFuture(DateTime time) { int type = widget.type; return createFutureType(type, time); } Future createFutureType(int type, DateTime time) async { Future>? data; DateTime now = widget.selectDate == null ? DateTime.now() : widget.selectDate!; switch (type) { case 0: data = api.getSportRecordListOneDay('${time.year}-${'${time.month}'.padLeft(2, '0')}-${'${time.day}'.padLeft(2, '0')}'); break; case 1: _initialPage = now.weekday - 1; DateTime start = DateTime(time.year, time.month, time.day - time.weekday + 1); DateTime end = DateTime(time.year, time.month, time.day + 6 - time.weekday + 1); data = api.getSportRecordListByDay('${start.year}-${'${start.month}'.padLeft(2, '0')}-${'${start.day}'.padLeft(2, '0')}', '${end.year}-${'${end.month}'.padLeft(2, '0')}-${'${end.day}'.padLeft(2, '0')}'); break; case 2: _initialPage = now.day - 1; DateTime start = DateTime(time.year, time.month, 1); DateTime end = DateTime(time.year, time.month + 1, 0); data = api.getSportRecordListByDay('${start.year}-${'${start.month}'.padLeft(2, '0')}-${'${start.day}'.padLeft(2, '0')}', '${end.year}-${'${end.month}'.padLeft(2, '0')}-${'${end.day}'.padLeft(2, '0')}'); break; case 3: _initialPage = now.month - 1; data = api.getSportRecordListByMonth(time.year); break; } _index = _initialPage = -1; if (data != null) { var simple = await data; if (simple.code == 0) { return SportDetail( target: simple.data?.target, exerDayTotal: simple.data?.exerDayTotal ?? 0, recordsTodaySum: simple.data?.sum ?? RecordsTodaySum(consume: 0, duration: 0, crouch: 0, jump: 0), recordsTodayAvg: simple.data?.avg ?? RecordsTodaySum(consume: 0, duration: 0, crouch: 0, jump: 0, times: 0, step: 0), recordsToday: simple.data?.records, targetFinish: simple.data?.targetFinish ?? [], exerDay: simple.data?.exerDay ?? []); } } return null; } String numToStr(num? v, {int asFixed = -1}) { return SportUtils.numToStr(v, asFixed: asFixed); } @override Widget build(BuildContext context) { super.build(context); final List xAxisList = xAxis(); final List dataList = data(); final List yAxisList = yAxis(dataList); int initialPage = _initialPage; String avgConsume = "0"; String avgStep = "0"; String avgDuration = "0"; List tabTitles = ["", "", "", ""]; if (_detail != null) { tabTitles[0] = numToStr(_detail?.recordsTodaySum?.consume); tabTitles[1] = numToStr(_detail?.recordsTodaySum?.step); tabTitles[2] = numToStr((_detail?.recordsTodaySum?.durationMin ?? 0), asFixed: 0); tabTitles[3] = "${_detail?.recordsTodaySum?.met.toStringAsFixed(1)}"; } if (widget.type != 0) { avgConsume = numToStr(_detail?.recordsTodayAvg?.consume); avgStep = numToStr(_detail?.recordsTodayAvg?.step); avgDuration = numToStr(_detail?.recordsTodayAvg?.durationMin ?? 0, asFixed: 0); } var _typeColor = Color(tabs[_tabIndex]["color"] as int); RecordsTodaySum? item = selectRecord(_index); var _bg = const Color(0xff241D19); return Scaffold( backgroundColor: _bg, body: RefreshIndicator( color: Theme.of(context).colorScheme.secondary, onRefresh: () async { _loadData(); }, child: CustomScrollView( slivers: [ SliverFillRemaining( child: Column( children: [ Container( color: _bg, child: Column( children: [ Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => PageNotification(page: 1).dispatch(context), child: Padding( padding: const EdgeInsets.all(25.0), child: arrowLeft(color: Colors.white), ), ), InkWell( onTap: () { SportDataPage.of(context)?.showCalendar(widget.selectDate ?? widget.time); UmengCommonSdk.onEvent("sport_data_calendar", {}); }, child: Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisSize: MainAxisSize.min, children: [ const SizedBox( width: 18, ), title(), const SizedBox( width: 4, ), Image.asset( "lib/assets/img/btn_date_bottom.png", width: 14, ), ], ), )), widget.index == 0 ? Padding( padding: const EdgeInsets.all(25.0), child: arrowRight(color: Color(0x90cccccc)), ) : GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => PageNotification(page: -1).dispatch(context), child: Padding( padding: const EdgeInsets.all(25.0), child: arrowRight(color: Colors.white), ), ), ], ), ), // if (widget.type != 0) // Padding( // padding: const EdgeInsets.fromLTRB(12.0, 0, 12.0, 20.0), // child: Row( // children: [ // Expanded( // child: Column( // children: [ // Text( // "日均大卡", // style: Theme // .of(context) // .textTheme // .bodyText1, // ), // const SizedBox( // height: 10, // ), // Text( // avgConsume, // style: Theme // .of(context) // .textTheme // .subtitle1 // ?.copyWith(fontSize: 25, fontFamily: "DIN", color: Colors.white), // ) // ], // ), // ), // Container( // width: 0.5, // height: 46, // color: Color(0xffDCDCDC).withOpacity(.3), // ), // Expanded( // child: Column( // children: [ // Text( // "日均步数", // style: Theme // .of(context) // .textTheme // .bodyText1, // ), // const SizedBox( // height: 10, // ), // Text( // avgStep, // style: Theme // .of(context) // .textTheme // .subtitle1 // ?.copyWith(fontSize: 25, fontFamily: "DIN", color: Colors.white), // ) // ], // ), // ), // Container( // width: 0.5, // height: 46, // color: Color(0xffDCDCDC).withOpacity(.3), // ), // Expanded( // child: Column( // children: [ // Text( // "周均分钟", // style: Theme // .of(context) // .textTheme // .bodyText1, // ), // const SizedBox( // height: 10, // ), // Text( // avgDuration, // style: Theme // .of(context) // .textTheme // .subtitle1 // ?.copyWith(fontSize: 25, fontFamily: "DIN", color: Colors.white), // ) // ], // ), // ) // ], // ), // ), AlignedGridView.count( shrinkWrap: true, crossAxisCount: 4, crossAxisSpacing: 8, padding: const EdgeInsets.fromLTRB(16.0, 10.0, 16.0, 0), physics: NeverScrollableScrollPhysics(), itemCount: 4, itemBuilder: (context, index) { var tab = tabs[index]; return InkWell( onTap: () { SportTypeNotification(index: index).dispatch(context); setState(() { _tabIndex = index; }); UmengCommonSdk.onEvent("sport_data_type_$index", {}); }, child: Column( children: [ Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 10.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), color: _tabIndex == index ? Color(tab["color"] as int) : Color(0xfff1f1f1).withOpacity(0.15), ), child: Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ Image.asset( "lib/assets/img/${_tabIndex == index ? tab["icon_select"] : tab["icon"]}.png", width: 16, ), const SizedBox( width: 5, ), Text( "${(tab["name"] is String) ? tab["name"] : (tab["name"] as List)[widget.type]}", style: Theme.of(context).textTheme.bodyText2?.copyWith(color: _tabIndex == index ? Colors.white : Color(0xff999999)), ), ], ), ), ), _tabIndex == index ? CustomPaint( painter: TrianglePath(), child: Container( height: 20, width: 20, ), ) : Container( height: 20, ), ], ), ); }, ), ], ), ), Expanded( child: Container( color: Colors.white, child: _detail == null ? RequestLoadingWidget() : Column( children: [ IndexedStack( index: _tabIndex, children: [ Header( index: 0, type: widget.type, params: { "date": "${_index < 0 ? "${TABS[widget.type]}总消耗" : item?.getDate(widget.type)}", "unit": "大卡", "total": "${item?.consume ?? tabTitles[_tabIndex]}", "current": "${widget.type > 0 && _index > -1 ? "1" : null}", "game": "游戏消耗", "jog": "跑步消耗", "daily": "日常消耗", "game_value": "${item?.consume_game ?? 0}", "jog_value": "${item?.consume_jog ?? 0}", "daily_value": "${item?.consume_daily ?? 0}", "times": "${item?.times}", "avg": "$avgConsume" }, recordsTodaySum: item, color: _typeColor, ), Header( index: 1, type: widget.type, params: { "date": "${_index < 0 ? "${TABS[widget.type]}总步数" : item?.getDate(widget.type)}", "unit": "步", "total": "${item?.step ?? tabTitles[_tabIndex]}", "current": "${widget.type > 0 && _index > -1 ? "1" : null}", "game": "游戏步数", "jog": "跑步步数", "daily": "日常步数", "game_value": "${item?.step_game ?? 0}", "jog_value": "${item?.step_jog ?? 0}", "daily_value": "${item?.step_daily ?? 0}", "times": "${item?.times}", "avg": "$avgStep" }, color: _typeColor, ), Header( index: 2, type: widget.type, params: { "date": "${_index < 0 ? "${TABS[widget.type]}总时长" : item?.getDate(widget.type)}", "unit": "分钟", "total": "${item?.durationMin.toStringAsFixed(0) ?? tabTitles[_tabIndex]}", "current": "${widget.type > 0 && _index > -1 ? "1" : null}", "game": "游戏时长", "jog": "跑步时长", "daily": widget.type == 0 || _index > -1 ? "运动目标" : "达标天数", "game_value": "${item?.durationMinGame.toStringAsFixed(0) ?? 0}", "jog_value": "${item?.durationMinJog.toStringAsFixed(0) ?? 0}", "daily_value": widget.type == 0 || _index > -1 ? "${(_detail?.target?.duration ?? 0) ~/ 60}" : "${yAxisList.where((element) => element > (_detail?.target?.duration ?? 0)).isNotEmpty ? yAxisList.where((element) => element > (_detail?.target?.duration ?? 0)).reduce((value, element) => value + element) : 0}", "times": "${item?.times}", "avg": "$avgDuration" }, settingDuration: widget.type == 0 || _index > -1, color: _typeColor, ), Header( index: 3, type: widget.type, params: { "date": "${_index < 0 ? "${TABS[widget.type]}MET" : item?.getDate(widget.type)}", "unit": "MET", "total": "${(item?.met.toStringAsFixed(1) ?? tabTitles[_tabIndex])}", "current": "${widget.type > 0 && _index > -1 ? "1" : null}", "game": "游戏消耗", "jog": "跑步消耗", "daily": "日常消耗", "game_value": "${item?.consume_game ?? 0}", "jog_value": "${item?.consume_jog ?? 0}", "daily_value": "${item?.consume_daily ?? 0}", "met": "强度评级", "met_value": "${metToLabel(item?.met ?? 0.0)}", "met_double": "${item?.met ?? 0.0}", "met_desc": "${metToDetail(item?.met ?? 0.0)}", }, color: _typeColor, ), ], ), Expanded(child: LayoutBuilder( builder: (context, size) { return Container( height: size.maxHeight, child: widget.type == 0 && _tabIndex == 3 ? Container( width: double.infinity, child: Center( child: Container( transform: Matrix4.translationValues(0, -100, 0), width: 300, height: 300, child: CustomPaint( painter: StrengthBg(), child: Container( child: Center( child: CircularPercentIndicator( radius: 200.0, lineWidth: 10.0, percent: (item?.met ?? 0) / 12.0, // percent: .7, center: Column( children: [ Text( "${item?.met.toStringAsFixed(1)}", style: Theme.of(context).textTheme.headline2!.copyWith(fontSize: 40.0, fontFamily: "DIN"), ), Text( "MET值", style: Theme.of(context).textTheme.subtitle2, ) ], mainAxisSize: MainAxisSize.min, ), animation: true, animationDuration: 1000, animateFromLastPercent: true, startAngle: 200.0, arcType: ArcType.CUSTOM_3, backgroundColor: Color(0xfff1f1f1), rotateLinearGradient: true, circularStrokeCap: CircularStrokeCap.butt, linearGradient: LinearGradient( colors: [Color(0xffFFE600), Color(0xffFF7323)], ), ), ), ), ), ), ), ) : ChartWidget( xAxis: xAxisList, yAxis: yAxisList, data: dataList, initialPage: initialPage, color: _typeColor, xAxisType: widget.type, chartHeight: size.maxHeight, targetLine: ((widget.type != 0 && widget.type != 3 && _tabIndex == 2) ? (_detail?.target?.duration.toDouble() ?? 0.0) : 0.0) / 60.0, unit: ["大卡", "步", "分钟", ""][_tabIndex], onTap: (index, drag) { final old = _index; setState(() { _index = drag ? index : _index == index ? -1 : index; }); return drag ? false : old == index; }, ), ); }, )) ], ), ), ), ], )) ], ), ), ); } List data() { switch (widget.type) { case 0: { List data = List.generate(24, (index) => 0); if (_detail != null) { for (var i = 0; i < (_detail!.recordsToday?.length ?? 0); i++) { var item = _detail!.recordsToday![i]; for (var j = 0; j < data.length; j++) { if (item.isSameHour(j)) { data[j] += item.getValue(_tabIndex); } } } } return data; } case 1: DateTime t = widget.time; DateTime week = DateTime(t.year, t.month, t.day).subtract(Duration(days: t.weekday - 1)); List data = List.filled(7, 0); if (_detail != null) { for (var i = 0; i < (_detail!.recordsToday?.length ?? 0); i++) { var item = _detail!.recordsToday![i]; for (var j = 0; j < data.length; j++) { if (item.isSameDay(week.add(Duration(days: j)).day)) { data[j] = item.getValue(_tabIndex); } } } } return data; case 2: DateTime t = widget.time; DateTime now = DateTime(t.year, t.month, 1); DateTime next = DateTime(t.year, t.month + 1, 1); int diff = now.difference(next).inDays.abs(); List data = List.filled(diff, 0); if (_detail != null) { for (var i = 0; i < (_detail!.recordsToday?.length ?? 0); i++) { var item = _detail!.recordsToday![i]; for (var j = 0; j < data.length; j++) { if (item.isSameDay(now.add(Duration(days: j)).day)) { data[j] = item.getValue(_tabIndex); } } } } return data; case 3: List data = List.filled(12, 0); if (_detail != null) { for (var i = 0; i < (_detail!.recordsToday?.length ?? 0); i++) { var item = _detail!.recordsToday![i]; data[(item.month) - 1] = item.getValue(_tabIndex); } } return data; default: return []; } } List xAxis() { switch (widget.type) { case 0: return ["00:00", "06:00", "12:00", "18:00", "00:00"]; case 1: DateTime t = widget.time; DateTime now = DateTime(t.year, t.month, t.day - t.weekday); return List.generate(7, (index) { DateTime time = DateTime(now.year, now.month, now.day + index + 1); return "${time.month}/${time.day}\n${WEEK[index]}"; }); case 2: DateTime t = widget.time; DateTime now = DateTime(t.year, t.month, 1); DateTime next = DateTime(t.year, t.month + 1, 1); int diff = now.difference(next).inDays.abs(); return ["1", "5", "10", "15", "20", "25", if (diff > 29) "30"]; case 3: return List.generate(12, (index) => "${index + 1}"); default: return []; } } List yAxis(List dataList) { if (dataList.isNotEmpty == true) { double max = dataList.reduce((value, element) => value > element ? value : element); max = math.max(max, ((widget.type != 0 && widget.type != 3 && _tabIndex == 2) ? (_detail?.target?.duration.toDouble() ?? 0.0) : 0.0) / 60.0); int count = 5; double split = max / count; int length = split.round().toString().length; int one = int.parse("1" + List.filled(math.max(0, length - 2), 0).join("")); int p = one; while (p < split) { p += one; } print("1111111111111111 max = $max $split $length $p"); // print("max $max $p $split"); return List.generate(count + 1, (index) => index * p.toDouble()); } return []; } RecordsTodaySum? selectRecord(int index) { if (_detail == null) return null; if (widget.type == 0 || index < 0) { return _detail?.recordsTodaySum; } DateTime t = widget.time; DateTime week = DateTime(t.year, t.month, t.day).subtract(Duration(days: t.weekday - 1)); var recordsToday = _detail?.recordsToday ?? []; if (recordsToday.isNotEmpty == true) { for (var element in recordsToday) { switch (widget.type) { case 1: if (element.isSameDay(week.add(Duration(days: index)).day)) return element; break; case 2: if (element.isSameDay(index + 1)) return element; break; case 3: if (element.month == index + 1) return element; break; } } } switch (widget.type) { case 0: return RecordsTodaySum(createdAt: "${t.year}-${t.month.toString().padLeft(2, '0')}-${t.day.toString().padLeft(2, '0')}"); case 1: return RecordsTodaySum(createdAt: "${t.year}-${t.month.toString().padLeft(2, '0')}-${(index + week.day).toString().padLeft(2, '0')}"); case 2: return RecordsTodaySum(createdAt: "${t.year}-${t.month.toString().padLeft(2, '0')}-${(index + 1).toString().padLeft(2, '0')}"); case 3: return RecordsTodaySum(createdAt: "${t.year}-${(index + 1).toString().padLeft(2, '0')}-01", year: t.year, month: index + 1); default: return null; } } @override bool get wantKeepAlive => true; void refresh() { _loadData(); } } class Header extends StatelessWidget { final int index; final int type; final Map params; final RecordsTodaySum? recordsTodaySum; final Color color; final bool settingDuration; const Header({Key? key, required this.index, required this.type, required this.params, this.recordsTodaySum, required this.color, this.settingDuration = false}) : super(key: key); @override Widget build(BuildContext context) { final _marginBox = EdgeInsets.zero; final current = params["current"] == "1"; final color = current ? this.color : const Color(0xff999999); final _textStyle = Theme.of(context).textTheme.subtitle1!.copyWith(fontSize: 14.0, color: const Color(0xff999999)); final _textStyleValue = Theme.of(context).textTheme.subtitle1!.copyWith(fontSize: 18.0, color: color, fontWeight: FontWeight.w500); return Container( decoration: circular(), padding: const EdgeInsets.all(16.0), margin: _marginBox, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!(index == 3 && type == 0)) Container( margin: const EdgeInsets.only(bottom: 16.0), height: 70, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ params["date"] != "null" ? Text( "${params["date"]}", style: Theme.of(context).textTheme.bodyText1?.copyWith(color: color), ) : Container(), Row( children: [ Text( "${params["total"]}", style: current ? Theme.of(context).textTheme.headline2!.copyWith(fontSize: 35.0, fontFamily: "DIN", color: this.color) : Theme.of(context).textTheme.headline2!.copyWith(fontSize: 35.0, fontFamily: "DIN"), ), Text( " ${params["unit"]}", style: current ? Theme.of(context).textTheme.subtitle2?.copyWith(color: this.color) : Theme.of(context).textTheme.subtitle2, ) ], crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, mainAxisSize: MainAxisSize.min, ), ], ), ), ), if (recordsTodaySum != null) FutureBuilder( future: Provider.of(context, listen: false).sport(recordsTodaySum), builder: (context, snapshot) { return InkWell( onTap: () async { if (await showDialog( context: context, builder: (context) => SportReferencePage( sum: recordsTodaySum!, ), ) == true) { SportDataDetailPageState? state = context.findAncestorStateOfType(); state?.refresh(); } }, child: Stack( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0), decoration: BoxDecoration(color: Theme.of(context).scaffoldBackgroundColor, borderRadius: BorderRadius.circular(50)), child: Center( child: Text( "相当于 ${snapshot.data ?? ""}", style: Theme.of(context).textTheme.bodyText1, ), ), ), Positioned(top: 0, bottom: 0, right: 16.0, child: arrowRight4()), ], ), ); }), if (!params.containsKey("met")) Padding( padding: const EdgeInsets.symmetric(vertical: 18.0, horizontal: 6), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "${params["game"]}", style: _textStyle, ), const SizedBox( height: 6, ), Text( "${params["game_value"]}", style: _textStyleValue, ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "${params["jog"]}", style: _textStyle, ), const SizedBox( height: 6, ), Text( "${params["jog_value"]}", style: _textStyleValue, ), ], ), settingDuration ? InkWell( onTap: () async { await NavigatorUtil.goPage(context, (context) => DurationSettingPage()); SportDataDetailPageState? state = context.findAncestorStateOfType(); state?.refresh(); }, child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Row( children: [ Text( "${params["daily"]}", style: _textStyle, ), const SizedBox( width: 5, ), arrowRight4() ], ), const SizedBox( height: 6, ), Text( "${params["daily_value"]}", style: _textStyleValue, ), ], ), ) : Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "${params["daily"]}", style: _textStyle, ), const SizedBox( height: 6, ), Text( "${params["daily_value"]}", style: _textStyleValue, ), ], ), ], ), ), if (params.containsKey("times")) const Divider( height: 1, ), if (params.containsKey("times")) Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "运动${params["times"]}次", style: Theme.of(context).textTheme.bodyText1, ), if (params["current"] != "1" && type != 0) Text("${index == 2 ? "周均" : "日均"}${params["avg"]}${params["unit"]}", style: Theme.of(context).textTheme.bodyText1), ], ), ), if (params.containsKey("met")) SizedBox( height: 20, ), if (params.containsKey("met")) Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "${params["met"]}", style: Theme.of(context).textTheme.headline3, ), Text( "${params["met_value"]}", style: _textStyle.copyWith(color: metToColor(double.parse(params["met_double"] ?? "0.0"))), ), ], ), ), if (params.containsKey("met")) Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Text( "${params["met_desc"]}", style: _textStyle.copyWith(fontSize: 14.0, color: Color(0xff999999), height: 1.5), )), SizedBox( height: 20.0, ), ], ), ); } } class ChartWidget extends StatefulWidget { final List xAxis; final List yAxis; final List data; final int initialPage; final int? xAxisType; final Color color; final double chartHeight; final double targetLine; final String? unit; final bool Function(int index, bool drag) onTap; const ChartWidget({Key? key, required this.xAxis, required this.yAxis, required this.data, this.initialPage = 0, this.xAxisType, required this.color, required this.chartHeight, this.targetLine = 0, this.unit, required this.onTap}) : super(key: key); @override State createState() => _ChartWidget(); } class _ChartWidget extends State { double _dx = 0; List area = []; onTap(Offset offset, bool drag) { var dx = offset.dx; for (var i = 0; i < area.length; i++) { var rect = area[i]; if (rect.contains(offset)) { if (widget.onTap(i, drag)) { dx = 0; } break; } } if (offset == Offset.zero) { widget.onTap(-1, drag); } setState(() { _dx = dx; }); } @override Widget build(BuildContext context) { return GestureDetector( onHorizontalDragStart: (details) { setState(() { _dx = details.localPosition.dx; }); }, onHorizontalDragUpdate: (details) { // print("${details.localPosition.dx} -- ${details.localPosition.dy}"); onTap(details.localPosition, true); }, onHorizontalDragCancel: () {}, onHorizontalDragEnd: (details) { if (details.velocity.pixelsPerSecond.dx > 0) { // 右滑 PageNotification(page: 1).dispatch(context); } else if (details.velocity.pixelsPerSecond.dx < 0) { // 左滑 PageNotification(page: -1).dispatch(context); } else {} onTap(Offset.zero, false); }, onTapUp: (details) { onTap(details.localPosition, false); }, child: Container( child: CustomPaint( size: Size.fromHeight(widget.chartHeight), painter: DataChart( callback: (area) { this.area = area; }, xAxis: widget.xAxis, yAxis: widget.yAxis, data: widget.data, dx: _dx, initialPage: widget.initialPage, color: widget.color, xAxisType: widget.xAxisType, targetLine: widget.targetLine, unit: widget.unit), ), ), ); } } class TrianglePath extends CustomPainter { @override void paint(Canvas canvas, Size size) { var path = Path(); path.moveTo(size.width / 2, size.height / 2); path.lineTo(0, size.height + 2); path.lineTo(size.width, size.height + 2); canvas.drawPath(path, Paint()..color = Colors.white); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }