123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639 |
- import 'dart:math';
- 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/application.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<StatefulWidget> createState() => _PageState();
- }
- class _PageState extends State<ConsumePage> with InjectApi {
- Color _color = Color(0xffFFC400);
- ValueNotifier<String> _tab = ValueNotifier<String>("日");
- ValueNotifier<SportDetail?> _valueNotifierSportDetail = ValueNotifier(null);
- ValueNotifier<DateTime> _valueNotifierDate = ValueNotifier(DateTime.now());
- ValueNotifier<DateTime> _valueNotifierNow = ValueNotifier(DateTime.now());
- late PageController _pageController;
- late 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: <Widget>[
- extended.ExtendedNestedScrollView(
- controller: _scrollController,
- pinnedHeaderSliverHeightBuilder: () {
- return pinnedHeaderHeight;
- },
- headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
- return <Widget>[
- SliverToBoxAdapter(
- child: Container(
- width: 240.0,
- height: headerHeight,
- padding: EdgeInsets.only(top: 16.0),
- child: Align(
- alignment: Alignment.center,
- child: CustomPaint(
- painter: _Bg(),
- child: Container(
- width: 180.0,
- height: 180.0,
- child: Center(
- child: Column(
- children: <Widget>[
- Text("消耗卡路里", style: Theme.of(context).textTheme.subtitle1!),
- SizedBox(
- height: 26.0,
- ),
- Row(
- children: <Widget>[
- Text(" ", style: Theme.of(context).textTheme.subtitle2),
- ValueListenableBuilder(
- builder: (BuildContext context, value, Widget? child) => FutureBuilder(
- future: createFutureType(0, _valueNotifierNow.value),
- builder: (BuildContext context, AsyncSnapshot<SportDetail?> 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: <Widget>[
- ValueListenableBuilder(
- valueListenable: _valueNotifierNow,
- builder: (BuildContext context, DateTime value, Widget? child) => Text("${value.month}.${value.day}", style: Theme.of(context).textTheme.subtitle1!),
- ),
- SizedBox(
- width: 6.0,
- ),
- Image.asset("lib/assets/img/setgoals_icon_date.png"),
- ],
- mainAxisSize: MainAxisSize.min,
- ),
- behavior: HitTestBehavior.opaque,
- )
- ],
- mainAxisSize: MainAxisSize.min,
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- 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: <Widget>[
- 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<DateTime>(
- 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: <Widget>[
- 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 FutureBuilder<SportDetail?>(
- key: PageStorageKey<String>('Tab$index'),
- future: createFuture(time),
- builder: (BuildContext context, AsyncSnapshot<SportDetail?> snapshot) {
- var _value = snapshot.data;
- if (_value == null) return Container();
- var _items = _createItems(type, _value.recordsTodaySum!);
- return snapshot.connectionState != ConnectionState.done
- ? RequestLoadingWidget()
- : SingleChildScrollView(
- child: Column(
- children: <Widget>[
- SizedBox(
- height: 30.0,
- ),
- 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: "大卡")..initData(maxValue: 3500.0 * (type + 1), valueSplit: 500),
- child: Container(
- height: 200,
- ),
- ),
- Padding(
- padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
- child: StaggeredGrid.extent(
- maxCrossAxisExtent: (MediaQuery.of(context).size.width - 32.0) / 2,
- 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: <Widget>[
- Image.asset(
- e.icon,
- width: 36.0,
- ),
- const SizedBox(
- width: 10.0,
- ),
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: <Widget>[
- Row(
- crossAxisAlignment: CrossAxisAlignment.end,
- children: <Widget>[
- 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(),
- ),
- ),
- if (type != 0 && _value.recordsTodayAvg != null)
- Padding(
- padding: const EdgeInsets.fromLTRB(8.0, 0, 8.0, 20.0),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: <Widget>[
- 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: <Widget>[
- Row(
- children: <Widget>[
- 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?.duration ?? 0) ~/ 60}分钟",
- style: Theme.of(context).textTheme.subtitle1!,
- ),
- ),
- ],
- ),
- const SizedBox(
- height: 20.0,
- ),
- Row(
- children: <Widget>[
- 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: <Widget>[
- 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: <Widget>[
- 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?.step ?? 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;
- });
- if (hash != null) {
- NavigatorUtil.goPage(
- context,
- (context) => WebViewSharePage(
- shareUrl,
- hash: hash,
- openShare: true,
- ));
- }
- },
- ),
- ),
- ],
- ),
- ),
- );
- }
- int toType() {
- return TABS.indexOf(_tab.value);
- }
- void changeTab() async {
- _valueNotifierSportDetail.value = null;
- if (_valueNotifierDate.value == null) return;
- Future<SportDetail?> data = createFuture(_valueNotifierDate.value);
- if (data != null) {
- data.then((value) => _valueNotifierSportDetail.value = value);
- }
- }
- Future<SportDetail?> createQueryFuture(int offset) {
- int type = toType();
- DateTime next = offsetDate(type, offset);
- return createFuture(next);
- }
- Future<SportDetail?> createFuture(DateTime time) async {
- int type = toType();
- return createFutureType(type, time);
- }
- Future<SportDetail?> createFutureType(int type, DateTime time) async {
- Future<RespData<SportDetailSimple>>? 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, 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);
- }
- }
- return null;
- }
- void rollDate(int offset) {
- if (_valueNotifierSportDetail.value == null) return;
- int type = toType();
- DateTime next = offsetDate(type, offset);
- _valueNotifierDate.value = next;
- // changeTab();
- }
- List<DataItem> _createItems(int type, RecordsTodaySum sum) {
- var lable = "";
- if (type == 1) {
- lable = "周";
- } else if (type == 2) {
- lable = "月";
- } else if (type == 3) {
- lable = "年";
- }
- return [
- DataItem("lib/assets/img/data_icon_consume.png", "${sum.consume}", "大卡", "$lable总消耗"),
- DataItem("lib/assets/img/data_icon_duration.png", "${(sum.duration) ~/ 60}", "分钟", "$lable总时长"),
- DataItem("lib/assets/img/data_icon_frequency.png", "${sum.times }", "次", "$lable运动次数"),
- DataItem("lib/assets/img/data_icon_steps.png", "${sum.step}", "歩", "$lable运动步数"),
- DataItem("lib/assets/img/data_icon_squat.png", "${sum.crouchRate.toStringAsFixed(0)}", "次/分钟", "下蹲频率"),
- DataItem("lib/assets/img/data_icon_jump.png", "${sum.jumpRate.toStringAsFixed(0)}", "次/分钟", "跳跃频率"),
- if (type == 0) DataItem("lib/assets/img/data_icon_strength.png", "${strengthToLabel(sum.consume, sum.duration)}", "", "强度评级")
- ];
- }
- }
- 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;
- }
|