import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart' as extended; import 'package:flutter/material.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:sport/bean/share_info.dart'; import 'package:sport/bean/sport_detail.dart'; import 'package:sport/bean/sport_index.dart'; import 'package:sport/pages/home/step_page.dart'; import 'package:sport/pages/social/share_webview.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/date.dart'; import 'package:sport/utils/toast.dart'; import 'package:sport/widgets/appbar.dart'; import 'package:sport/widgets/chart.dart'; import 'package:sport/widgets/decoration.dart'; import 'package:sport/widgets/dialog/request_dialog.dart'; import 'package:sport/widgets/image.dart'; import 'package:sport/widgets/loading.dart'; import 'package:sport/widgets/misc.dart'; import 'package:sport/widgets/persistent_header.dart'; class ConsumePage extends StatefulWidget { @override State createState() => _PageState(); } class _PageState extends State with InjectApi { Color _color = Color(0xffFFC400); ValueNotifier _tab = ValueNotifier("日"); ValueNotifier _valueNotifierSportDetail = ValueNotifier(null); ValueNotifier _valueNotifierDate = ValueNotifier(DateTime.now()); ValueNotifier _valueNotifierNow = ValueNotifier(DateTime.now()); PageController _pageController; ScrollController _scrollController; @override void initState() { super.initState(); _pageController = PageController(initialPage: 0); _scrollController = ScrollController(); changeTab(); } @override void dispose() { _pageController?.dispose(); super.dispose(); _tab?.dispose(); _valueNotifierSportDetail?.dispose(); _valueNotifierDate?.dispose(); _valueNotifierNow?.dispose(); _scrollController?.dispose(); } @override Widget build(BuildContext context) { final double tabHeader = 90.0; final double statusBarHeight = MediaQuery.of(context).padding.top; final double pinnedHeaderHeight = tabHeader; final double headerHeight = 240.0; return Scaffold( backgroundColor: Colors.white, body: SafeArea( child: Stack( children: [ extended.NestedScrollView( controller: _scrollController, pinnedHeaderSliverHeightBuilder: () { return pinnedHeaderHeight; }, innerScrollPositionKeyBuilder: () { PageController controller = _pageController; String index = 'Tab${controller.page}'; return Key(index); }, headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return [ SliverToBoxAdapter( child: Container( width: 240.0, height: headerHeight, child: Align( alignment: Alignment.center, child: CustomPaint( painter: _Bg(), child: Container( width: 180.0, height: 180.0, child: Center( child: Column( children: [ Text("消耗卡路里", style: Theme.of(context).textTheme.subtitle1), SizedBox( height: 26.0, ), Row( children: [ ValueListenableBuilder( builder: (BuildContext context, value, Widget child) => FutureBuilder( future: createFutureType(0, _valueNotifierNow.value), builder: (BuildContext context, AsyncSnapshot snapshot) => Text( "${snapshot?.data?.recordsTodaySum?.consume ?? 0}", style: Theme.of(context).textTheme.headline1.copyWith(fontSize: 40.0, fontFamily: "DIN"), strutStyle: fixedLine, ), ), valueListenable: _valueNotifierNow, ), Text(" 卡", style: Theme.of(context).textTheme.subtitle2), ], mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, ), SizedBox( height: 8, ), GestureDetector( onTap: () async { var result = await showDatePicker( context: context, initialDate: _valueNotifierNow.value, lastDate: DateTime.now(), firstDate: DateTime(2020), ); if (result != null) { var diff = DateTime.now().difference(result); _valueNotifierDate.value = result; _valueNotifierNow.value = result; int type = toType(); // if (type == 0) { // _pageController.jumpToPage(diff.inDays); // } else { // _pageController = PageController(initialPage: diff.inDays); // } _tab.value = TABS.first; _pageController.jumpToPage(diff.inDays); print("$type -- ${diff.inDays}"); } }, child: Row( children: [ ValueListenableBuilder( valueListenable: _valueNotifierNow, builder: (BuildContext context, DateTime value, Widget child) => Text("${value.month}.${value.day}", style: Theme.of(context).textTheme.subtitle1), ), SizedBox( width: 12.0, ), Image.asset("lib/assets/img/setgoals_icon_date.png"), ], mainAxisSize: MainAxisSize.min, ), behavior: HitTestBehavior.opaque, ) ], mainAxisSize: MainAxisSize.min, ), ), margin: EdgeInsets.only(top: 16.0), ), ), ), ), ), SliverPersistentHeader( pinned: true, delegate: PersistentHeader( min: pinnedHeaderHeight, max: pinnedHeaderHeight, child: Container( color: Colors.white, child: ValueListenableBuilder( valueListenable: _tab, builder: (BuildContext context, String value, Widget child) { return Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 5.0), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: ["日", "/", "周", "/", "月", "/", "年"] .map((e) => e == "/" ? Container( margin: const EdgeInsets.fromLTRB(5, 0, 1, 0), color: const Color(0xffdcdcdc), width: 0.5, height: 14, transform: Matrix4.rotationZ(0.35), ) : InkWell( onTap: () { if (_valueNotifierSportDetail.value == null) return; _scrollController.animateTo(headerHeight, duration: Duration(milliseconds: 500), curve: Curves.linear); _tab.value = e; _valueNotifierDate.value = DateTime.now(); _pageController.jumpToPage(0); // _pageController= PageController(initialPage: 0); changeTab(); }, child: Container( margin: EdgeInsets.symmetric(horizontal: 12.0), padding: EdgeInsets.all(8.0), decoration: value == e ? BoxDecoration(color: _color, shape: BoxShape.circle) : null, child: Text( "$e", style: value == e ? Theme.of(context).textTheme.subtitle1.copyWith(color: Colors.white) : Theme.of(context).textTheme.subtitle1, ), ))) .toList(), ), ), const SizedBox(height: 10.0,), Center( child: ValueListenableBuilder( valueListenable: _valueNotifierDate, builder: (_, time, ___) { int type = toType(); String text = ""; if (type == 0) { text = "${time.year}.${'${time.month}'.padLeft(2, '0')}.${'${time.day}'.padLeft(2, '0')} 6:00 - 24:00 "; } 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); print("$time ${time.weekday} == $start $end"); text = "${start.year}.${'${start.month}'.padLeft(2, '0')}.${'${start.day}'.padLeft(2, '0')} ~ ${end.year}.${'${end.month}'.padLeft(2, '0')}.${'${end.day}'.padLeft(2, '0')}"; } else if (type == 2) { text = ("${time.year}年${'${time.month}'.padLeft(2, '0')}月"); } else if (type == 3) { text = ("${time.year}年"); } return Row( mainAxisSize: MainAxisSize.min, children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { _pageController?.nextPage(duration: Duration(milliseconds: 500), curve: Curves.linear); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 10.0), child: arrowLeft(), ), ), Text( text, style: Theme.of(context).textTheme.bodyText2.copyWith(color: Color(0xff333333)), strutStyle: fixedLine, ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { if (_pageController?.page == 0.0) { ToastUtil.show("没有数据了"); return; } _pageController?.previousPage(duration: Duration(milliseconds: 500), curve: Curves.linear); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 10.0), child: arrowRight(), ), ), ], ); }), ), ], ); }, ), ), ), ), ]; }, body: ValueListenableBuilder( valueListenable: _tab, builder: (BuildContext context, String value, Widget child) => PageView.builder( reverse: true, itemCount: 10240, controller: _pageController, onPageChanged: (page) { rollDate(-page); }, itemBuilder: (context, index) { int type = toType(); DateTime time = offsetDate(type, -index); print("$index $type --2222 ${time}"); return extended.NestedScrollViewInnerScrollPositionKeyWidget( Key('Tab$index'), FutureBuilder( future: createFuture(time), builder: (BuildContext context, AsyncSnapshot snapshot) { var _value = snapshot?.data; var _items = _createItems(type, _value?.recordsTodaySum); return snapshot.connectionState != ConnectionState.done ? RequestLoadingWidget() : Padding( padding: const EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 0), child: SingleChildScrollView( child: Column( children: [ SizedBox( height: 10.0, ), Padding( padding: const EdgeInsets.only(right: 12.0), child: CustomPaint( painter: Chart( type: TABS.indexOf(_tab.value), records: _value?.recordsToday ?.map((e) => ChartItem(type == 3 ? "${e.month}" : e.createdAt, e.consume)) ?.toList() ?? [], dateTime: time, drawMax: true, unit: "kal") ..initData(maxValue: 3500.0 * (type + 1), valueSplit: 500), child: Container( height: 200, ), ), ), const SizedBox( width: 12.0, ), Padding( padding: const EdgeInsets.fromLTRB(8, 8, 8, 4), child: StaggeredGridView.extent( maxCrossAxisExtent: (MediaQuery.of(context).size.width - 32.0) / 2, padding: EdgeInsets.zero, shrinkWrap: true, physics: NeverScrollableScrollPhysics(), mainAxisSpacing: 12.0, crossAxisSpacing: 12.0, children: _items .map((e) => Container( decoration: card(), padding: const EdgeInsets.fromLTRB(20.0, 20.0, 0, 20.0), child: Row( children: [ Image.asset( e.icon, width: 36.0, ), const SizedBox( width: 10.0, ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( e.title, style: e.unit != "" ? Theme.of(context).textTheme.headline1.copyWith(fontSize: 20.0):Theme.of(context).textTheme.headline1.copyWith(fontSize: 16.0), strutStyle: fixedLine, ), Text(" ${e.unit}", style: Theme.of(context).textTheme.subtitle2), ], ), const SizedBox( width: 4.0, ), Text(e.subtitle, style: Theme.of(context).textTheme.bodyText1) ], ) ], ), )) .toList(), staggeredTiles: _items.map((e) => StaggeredTile.fit(1)).toList()), ), if (type != 0 && _value?.recordsTodayAvg != null) Padding( padding: const EdgeInsets.fromLTRB(8.0, 0, 8.0, 20.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "日均数据", style: Theme.of(context).textTheme.headline1.copyWith(fontSize: 16.0), ), SizedBox( height: 16.0, ), Container( padding: const EdgeInsets.fromLTRB(14.0, 21.0, 14.0, 21.0), decoration: card(), child: Column( children: [ Row( children: [ Image.asset( "lib/assets/img/day_icon_duration.png", width: 19.0, ), const SizedBox( width: 8.0, ), Expanded( child: Text( "日均时长", style: Theme.of(context).textTheme.subtitle1, ), ), SizedBox( width: 60.0, child: Text( "${_value?.recordsTodayAvg?.durationMinute ?? 0}分钟", style: Theme.of(context).textTheme.subtitle1, ), ), ], ), const SizedBox( height: 20.0, ), Row( children: [ Image.asset( "lib/assets/img/day_icon_consume.png", width: 19.0, ), const SizedBox( width: 8.0, ), Expanded( child: Text( "日均消耗", style: Theme.of(context).textTheme.subtitle1, ), ), SizedBox( width: 60.0, child: Text( "${_value?.recordsTodayAvg?.consume ?? 0}卡", style: Theme.of(context).textTheme.subtitle1, ), ), ], ), const SizedBox( height: 20.0, ), Row( children: [ Image.asset( "lib/assets/img/day_icon_frequency.png", width: 19.0, ), const SizedBox( width: 8.0, ), Expanded( child: Text( "日均运动次数", style: Theme.of(context).textTheme.subtitle1, ), ), SizedBox( width: 60.0, child: Text( "${_value?.recordsTodayAvg?.times ?? 0}次", style: Theme.of(context).textTheme.subtitle1, ), ), ], ), const SizedBox( height: 20.0, ), Row( children: [ Image.asset( "lib/assets/img/day_icon_steps.png", width: 19.0, ), const SizedBox( width: 8.0, ), Expanded( child: Text( "游戏步数", style: Theme.of(context).textTheme.subtitle1, ), ), SizedBox( width: 60.0, child: Text( "${_value?.recordsTodayAvg?.stepCount ?? 0}", style: Theme.of(context).textTheme.subtitle1, ), ), ], ) ], ), ) ], ), ) ], ), ), ); })); }, ), ), ), Positioned( child: buildBackButton(context), ), Positioned( right: 0, child: IconButton( icon: Image.asset("lib/assets/img/bbs_icon_share.png"), onPressed: () async { String hash; print("------------------------------------------------------------"); await request(context, () async { ShareInfo _info = (await api.getshareCreateSport("day", 50.0)).data; hash = _info.hash; print("[hash:]---------------------------${_info.hash}"); }); if (hash != null) { NavigatorUtil.goPage( context, (context) => WebViewSharePage( "http://shoes-web.hiyd.com/share", hash: hash, openShare: true, )); } }, ), ), ], ), ), ); } int toType() { return TABS.indexOf(_tab.value); } void changeTab() async { _valueNotifierSportDetail.value = null; if (_valueNotifierDate.value == null) return; Future data = createFuture(_valueNotifierDate.value); if (data != null) { data.then((value) => _valueNotifierSportDetail.value = value); } } Future createQueryFuture(int offset) { int type = toType(); DateTime next = offsetDate(type, offset); return createFuture(next); } Future createFuture(DateTime time) async { int type = toType(); return createFutureType(type, time); } Future createFutureType(int type, DateTime time) async { Future> data; switch (type) { case 0: data = api.getSportRecordListOneDay('${time.year}-${'${time.month}'.padLeft(2, '0')}-${'${time.day}'.padLeft(2, '0')}'); break; case 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: 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: data = api.getSportRecordListByMonth(time.year); break; } if (data != null) { var simple = await data; if (simple.code == 0) { return SportDetail( recordsTodaySum: simple.data.sum ?? RecordsTodaySum(consume: 0, duration: 0, crouchCount: 0, jumpCount: 0), recordsTodayAvg: simple.data.avg ?? RecordsTodaySum(consume: 0, duration: 0, crouchCount: 0, jumpCount: 0, durationMinute: 0, times: 0, stepCount: 0), recordsToday: simple.data.records); } } return null; } void rollDate(int offset) { if (_valueNotifierSportDetail.value == null) return; int type = toType(); DateTime next = offsetDate(type, offset); _valueNotifierDate.value = next; // changeTab(); } List _createItems(int type, RecordsTodaySum sum) { return [ DataItem("lib/assets/img/data_icon_consume.png", "${sum?.consume ?? 0}", "卡", "消耗卡路里"), DataItem("lib/assets/img/data_icon_duration.png", "${sum?.durationMinute ?? 0}", "分钟", "总时长"), DataItem("lib/assets/img/data_icon_frequency.png", "${sum?.times ?? 0}", "次", "运动次数"), DataItem("lib/assets/img/data_icon_steps.png", "${sum?.stepCount ?? 0}", "歩", "运动步数"), DataItem("lib/assets/img/data_icon_squat.png", "${sum?.crouchRate?.toStringAsFixed(0) ?? 0}", "次/分钟", "下蹲频率"), DataItem("lib/assets/img/data_icon_jump.png", "${sum?.jumpRate?.toStringAsFixed(0) ?? 0}", "次/分钟", "跳跃频率"), if (type == 0) DataItem("lib/assets/img/data_icon_strength.png", "${strengthToLabel(sum?.consume ?? 0)}", "", "强度评级") ]; } } class DataItem { final String icon, title, unit, subtitle; DataItem(this.icon, this.title, this.unit, this.subtitle); } class _Bg extends CustomPainter { final Paint _paint = Paint()..isAntiAlias = true; Color _color = const Color(0xffFFD736); @override void paint(Canvas canvas, Size size) { final Offset center = Offset(size.width / 2, size.height / 2); double radius = size.width / 2; // print("$size $center $radius"); _paint.color = _color.withOpacity(.2); canvas.drawCircle(center, radius, _paint); _paint.color = _color.withOpacity(.5); canvas.drawCircle(center, radius - 10, _paint); _paint.color = _color; canvas.drawCircle(center, radius - 20, _paint); } @override bool shouldRepaint(CustomPainter oldDelegate) => false; }