import 'dart:async'; import 'dart:math'; import 'dart:ui'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; import 'package:flutter_easyrefresh/easy_refresh.dart'; import 'package:provider/provider.dart'; import 'package:sport/bean/feed_back.dart'; import 'package:sport/bean/game.dart'; import 'package:sport/bean/share_info.dart'; import 'package:sport/bean/sport_detail.dart'; import 'package:sport/bean/sport_target.dart'; import 'package:sport/bean/sport_target_today.dart'; import 'package:sport/pages/home/step_page.dart'; import 'package:sport/pages/home/target_modify.dart'; import 'package:sport/pages/my/feedback_detail_page.dart'; import 'package:sport/pages/my/level_page.dart'; import 'package:sport/pages/social/share_webview.dart'; import 'package:sport/provider/bluetooth.dart'; import 'package:sport/provider/user_model.dart'; import 'package:sport/router/navigator_util.dart'; import 'package:sport/router/routes.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/alert_dialog.dart'; import 'package:sport/widgets/dialog/request_dialog.dart'; import 'package:sport/widgets/image.dart'; import 'package:sport/widgets/label.dart'; import 'package:sport/widgets/loading.dart'; import 'package:sport/widgets/misc.dart'; import 'package:sport/widgets/popmenu_bg.dart'; import 'package:sport/widgets/space.dart'; class SportDetailPage extends StatefulWidget { @override State createState() => _PageState(); } class _PageState extends State with TickerProviderStateMixin, InjectApi, InjectLoginApi { final List _TABS = ["日", "周", "月", "年"]; ValueNotifier _tab = ValueNotifier("日"); ValueNotifier _valueNotifierTarget = ValueNotifier(null); ValueNotifier _valueNotifierSportDetail = ValueNotifier(null); ValueNotifier _valueNotifierDate = ValueNotifier(DateTime.now()); int _durationTarget = 60; SportDetail _sportDetail; PageController _pageController; int _page = 0; StreamSubscription streamSubscription; EasyRefreshController _easyRefreshController; @override void initState() { super.initState(); _easyRefreshController = EasyRefreshController(); _pageController = PageController(initialPage: 0) ..addListener(() { _page = _pageController.page.toInt(); }); streamSubscription = Provider.of(context, listen: false).queryStream.listen((event) { refreshData(); }); refreshData(); Provider.of(context, listen: false).queryDeviceStep(); } refreshData() { api.getSportDetail().then((value) { _sportDetail = value.data; if (_sportDetail == null) return; _durationTarget = value.data.durationTarget; _valueNotifierSportDetail.value = _sportDetail; _valueNotifierTarget.value = SportTargetToday(remain: value.data.durationTarget - value.data.durationTotalDay, target: value.data.target); if (mounted) setState(() {}); if (value.data.target == null) { _showFirstTarget(); } }); } @override void dispose() { super.dispose(); _pageController?.dispose(); _tab?.dispose(); _valueNotifierTarget?.dispose(); _valueNotifierSportDetail?.dispose(); _valueNotifierDate?.dispose(); streamSubscription?.cancel(); _easyRefreshController?.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ EasyRefresh.builder( controller: _easyRefreshController, enableControlFinishRefresh: true, header: buildClassicalHeader(), onRefresh: () async { Provider.of(context, listen: false).queryDeviceStep(); await Future.delayed(Duration(seconds: 5)); _easyRefreshController.finishRefresh(success: true); }, builder: (context, physics, header, footer) { return CustomScrollView( physics: physics, slivers: [ buildSliverAppBar(context, "运动详情", backgroundColor: Theme.of(context).scaffoldBackgroundColor, actions: [ PopupMenuTheme( data: PopupMenuThemeData(shape: PopmenuShape(borderRadius: BorderRadius.all(Radius.circular(10.0)))), child: PopupMenuButton( padding: EdgeInsets.zero, offset: Offset(0, kToolbarHeight / 2 + 15), icon: Image.asset("lib/assets/img/topbar_more.png"), onSelected: (action) async { switch (action) { case "modify": NavigatorUtil.go(context, Routes.targetModify).then((value) { if (value == null) return; if (value is SportTargetToday) { _valueNotifierTarget.value = value; Provider.of(context, listen: false).durationTarget.value = value.target.duration; } }); break; case "data": FeedTypeInfoData group; await request(context, () async { FeedTypeInfo _feedTypeInfo = await loginApi.getFeedBackTypes(); if (_feedTypeInfo.code == 0) { group = _feedTypeInfo.data.singleWhere((element) => element.groupId == '2'); } }); if (group != null) { print("[group:]---------------------------$group"); NavigatorUtil.goPage(context, (context) => FeedbackDetailPage(group)); } break; case "share": 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,)); } break; } }, itemBuilder: (_) => [ menuItem("modify", "linkpop_icon_modify.png", "修改目标"), menuDivider(), menuItem("data", "linkpop_icon_modify_1.png", "数据报错"), menuDivider(), menuItem("share", "linkpop_icon_share.png", "数据分享"), ], ), ) ]), header, SliverToBoxAdapter( child: _buildTargetWidget(), ), SliverToBoxAdapter( child: _buildDataWidget(), ), SliverToBoxAdapter( child: _buildAchievementWidget(), ), SliverToBoxAdapter( child: _buildSportListWidget(), ), ], ); }), Positioned( right: -1, top: kToolbarHeight + MediaQuery.of(context).padding.top, child: GestureDetector( onTap: () => NavigatorUtil.goPage(context, (context) => StepPage()), child: Container( padding: EdgeInsets.fromLTRB(3, 3, 12, 3), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only(topLeft: Radius.circular(30), bottomLeft: Radius.circular(30)), border: Border.all( color: Theme.of(context).scaffoldBackgroundColor, width: .5, ), ), child: Row( children: [ Image.asset("lib/assets/img/gamedetail_step.png"), ValueListenableBuilder( valueListenable: _valueNotifierSportDetail, builder: (_, _value, ___) => Text( " ${_value?.step ?? 0}步", style: Theme.of(context).textTheme.subtitle1, )) ], ), ), ), ) ], ), ); } Widget _buildTargetWidget() { return ValueListenableBuilder( valueListenable: _valueNotifierTarget, builder: (_, _value, ___) => _value == null ? RequestLoadingWidget() : Column( children: [ Padding( padding: const EdgeInsets.only(right: 12.0, top: 15), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LabelWidget(title: "今日目标"), _value.target != null ? Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("目标时长:${_value.target.duration}分钟\t\t\t\t强度:${_value.target.intension}", style: Theme.of(context).textTheme.bodyText1), ], ) : InkWell( onTap: () { NavigatorUtil.go(context, Routes.targetModify).then((value) { if (value == null) return; if (value is SportTargetToday) { _valueNotifierTarget.value = value; } }); }, child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Text("设立目标", style: Theme.of(context).textTheme.subtitle1.copyWith(color: Theme.of(context).accentColor)), Space( width: 5, ), arrowRight6() ], ), ), ], ), ), Container( margin: EdgeInsets.fromLTRB(12.0, 6.0, 12.0, 18.0), padding: EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0), decoration: circular(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("运动时长", style: Theme.of(context).textTheme.subtitle1), Space( height: 12, ), Container(child: LayoutBuilder(builder: (context, box) { int minute = _sportDetail.durationTotalDay; double percent = minute / (_value.target == null ? _durationTarget : _value.target.duration); double w = 70; double padding = box.maxWidth * percent - w / 2; if (padding < w / 2) { padding = 0; } if (padding > box.maxWidth - w) { padding = box.maxWidth - w; } // fix 一直在最左! padding = 0; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(left: padding), child: Container( width: w, alignment: padding == 0 ? Alignment.centerLeft : Alignment.center, child: RichText( text: TextSpan(style: Theme.of(context).textTheme.subtitle2, children: [ TextSpan( text: '$minute', style: Theme.of(context).textTheme.subtitle2.copyWith(fontWeight: FontWeight.bold, fontSize: 25), ), TextSpan(text: '分钟'), ]), ), ), ), Space( height: 4, ), CustomPaint( painter: _ProgressBar(percent), child: Container( height: 12, ), ), ], ); })), Space( height: 15, ), Row( children: [ Text( "消耗(kal)", style: Theme.of(context).textTheme.subtitle2, ), Space( width: 6, ), Text( "${_sportDetail?.recordsTodaySum?.consume ?? 0}", style: Theme.of(context).textTheme.headline1.copyWith(color: Theme.of(context).accentColor), ), Space( width: 30, ), Text( "累计运动 (次)", style: Theme.of(context).textTheme.subtitle2, ), Space( width: 6, ), Text( "${_sportDetail?.recordsTodaySum?.times ?? 0}", style: Theme.of(context).textTheme.headline1.copyWith(color: Theme.of(context).accentColor), ), ], ), SizedBox( height: 4, ), Divider(), SizedBox( height: 4, ), if (_sportDetail?.nextAchievement != null) InkWell( onTap: () => NavigatorUtil.goAchievementDetails(context, id: _sportDetail.nextAchievement.achievement.id), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ RichText( text: TextSpan(style: Theme.of(context).textTheme.bodyText1, children: [ TextSpan(text: '距离'), TextSpan( text: ' ${_sportDetail.nextAchievement.achievement?.name} ', style: Theme.of(context).textTheme.bodyText1.copyWith(color: Theme.of(context).accentColor), ), TextSpan( text: '还需运动 ${_sportDetail.nextAchievement.diff ?? 0}${_sportDetail.nextAchievement.achievement?.conditionMeasure ?? ''}'), ])), arrowRight4() ], ), ), ], ), ), ], ), ); } Widget _buildDataWidget() { return Column( children: [ LabelWidget(title: "数据统计"), Container( margin: EdgeInsets.fromLTRB(12.0, 6.0, 12.0, 18.0), padding: EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0), decoration: circular(), child: Column( children: [ ValueListenableBuilder( valueListenable: _tab, builder: (BuildContext context, String value, Widget child) { return Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: ["日", "/", "周", "/", "月", "/", "年"] .map((e) => e == "/" ? Container( margin: EdgeInsets.symmetric(horizontal: 6.0), color: Color(0xffdcdcdc), width: 0.5, height: 14, transform: Matrix4.rotationZ(0.35), ) : InkWell( onTap: () { if (_valueNotifierSportDetail.value == null) return; _tab.value = e; _valueNotifierDate.value = DateTime.now(); // _pageController = PageController(initialPage: 0); _pageController.jumpToPage(0); changeTab(DateTime.now()); }, child: Container( margin: EdgeInsets.symmetric(horizontal: 12.0), padding: EdgeInsets.all(8.0), decoration: value == e ? BoxDecoration(color: Theme.of(context).accentColor, shape: BoxShape.circle) : null, child: Text( "$e", style: Theme.of(context).textTheme.subtitle1.copyWith(color: value == e ? Colors.white : Color(0xff333333)), ), ))) .toList(), ), ], ); }, ), ValueListenableBuilder( valueListenable: _tab, builder: (_, time, ___) => Container( height: 520, child: PageView.builder( reverse: true, itemCount: 10240, controller: _pageController, onPageChanged: (page) {}, itemBuilder: (context, index) { var time = offsetDate(toType(), -index); return FutureBuilder( future: changeTab(time), builder: (BuildContext context, AsyncSnapshot snapshot) { SportDetail _value = snapshot.data; return snapshot.connectionState != ConnectionState.done ? RequestLoadingWidget() : Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 12), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { _pageController?.nextPage(duration: Duration(milliseconds: 500), curve: Curves.linear); }, child: Padding( padding: const EdgeInsets.all(16.0), child: arrowLeft(), ), ), Expanded( child: Center( child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { int type = toType(); 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]}"); } 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"); return 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) { return Text("${time.year}年${'${time.month}'.padLeft(2, '0')}月"); } else if (type == 3) { return Text("${time.year}年"); } return Text(" "); }), ), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { if (_pageController?.page == 0.0) { return; } _pageController?.previousPage(duration: Duration(milliseconds: 500), curve: Curves.linear); }, child: Padding( padding: const EdgeInsets.all(16.0), child: arrowRight(), ), ), ], ), ), Space( height: 12, ), CustomPaint( painter: Chart(type: _TABS.indexOf(_tab.value),records: _value?.recordsToday?.map((e) => ChartItem(e.createdAt, e.consume))?.toList(), dateTime: time)..initData(), child: Container( height: 160, ), ), Space( height: 6, ), Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "总消耗", style: Theme.of(context).textTheme.subtitle1, ), Text("${_value == null ? ".." : "${_value.recordsTodaySum.consume}"} kal", style: Theme.of(context).textTheme.subtitle1) ], ), Space( height: 8, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "总时长", style: Theme.of(context).textTheme.subtitle1, ), Text("${_value == null ? ".." : "${_value.recordsTodaySum.duration}"}分钟", style: Theme.of(context).textTheme.subtitle1) ], ), Space( height: 8, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "累计运动", style: Theme.of(context).textTheme.subtitle1, ), Text("${_value == null ? ".." : "${_value.recordsTodaySum.times ?? 0}"}次", style: Theme.of(context).textTheme.subtitle1) ], ), Space( height: 8, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "跳跃次数", style: Theme.of(context).textTheme.subtitle1, ), Text("${_value == null ? ".." : "${_value.recordsTodaySum.jumpCount}"}次", style: Theme.of(context).textTheme.subtitle1) ], ), Space( height: 8, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "下蹲次数", style: Theme.of(context).textTheme.subtitle1, ), Text("${_value == null ? ".." : "${_value.recordsTodaySum.crouchCount}"}次", style: Theme.of(context).textTheme.subtitle1) ], ), Space( height: 8, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "游戏步数", style: Theme.of(context).textTheme.subtitle1, ), Text("${_value == null ? ".." : "${_value.recordsTodaySum?.stepCount ?? 0}"}次", style: Theme.of(context).textTheme.subtitle1) ], ), Row( children: [ Expanded(child: Divider()), Padding( padding: const EdgeInsets.all(16.0), child: Text( "相当于", style: Theme.of(context).textTheme.bodyText1, ), ), Expanded(child: Divider()) ], ), sportBeEquivalentTo( context, _value == null || _value.recordsTodaySum == null ? 0 : _value.recordsTodaySum.consume), ], ), Space( height: 10, ) ], ); }); }, ), )), ], ), ), ], ); } Widget _buildAchievementWidget() { return Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LabelWidget(title: "我的成就"), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { NavigatorUtil.goPage(context, (context) => LevelPage()); }, child: Padding( padding: const EdgeInsets.fromLTRB(12.0, 6.0, 12.0, 6.0), child: Row( children: [ Text( "更多", style: Theme.of(context).textTheme.bodyText1, ), Space( width: 4, ), arrowRight4() ], ), ), ) ], ), Container( margin: EdgeInsets.fromLTRB(12.0, 6.0, 12.0, 18.0), padding: EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0), width: double.infinity, decoration: circular(), child: Padding( padding: const EdgeInsets.symmetric(vertical: 12.0), child: achievementGroupWidget(_sportDetail?.achievements), ), ), ], ); } Widget _buildSportListWidget() { return _sportDetail?.gamesSum?.isNotEmpty == true ? Column( children: [ LabelWidget(title: "运动统计"), Container( margin: EdgeInsets.fromLTRB(12.0, 6.0, 12.0, 18.0), padding: EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0), decoration: circular(), child: ListView.separated( padding: EdgeInsets.zero, shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemBuilder: (context, index) { GamesSum sum = _sportDetail.gamesSum[index]; return Column( children: [ _buildWrapListHeaderWidget(sum), _buildWrapListTileWidget("总消耗:", "${sum.consumeTotal}kal"), _buildWrapListTileWidget("总时长:", "${sum.durationTotal}分钟"), _buildWrapListTileWidget("运动效率:", "${(sum.consumeTotal / sum.durationTotal).toStringAsFixed(2)}kal/min"), _buildWrapListTileWidget("总局数:", "${sum.playCount}局"), _buildWrapListTileWidget("最高得分:", "${sum.scoreMax.toStringAsFixed(1)}"), Padding( padding: const EdgeInsets.all(8.0), child: InkWell( onTap: () => NavigatorUtil.goGameHistory(context, sum.game), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( "查看记录详情 ", style: Theme.of(context).textTheme.bodyText1, ), arrowRight4() ], ), ), ) ], ); }, separatorBuilder: (context, index) => Divider(), itemCount: _sportDetail.gamesSum.length), ), ], ) : Container(); } Widget _buildWrapListHeaderWidget(GamesSum sum) { GameInfoData game = sum.game; return Row( children: [ Container( width: 50, height: 50, margin: EdgeInsets.fromLTRB(0, 6, 8, 6), decoration: BoxDecoration(borderRadius: BorderRadius.circular(6), image: DecorationImage(fit: BoxFit.cover, image: CachedNetworkImageProvider(game.cover))), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( game.name, style: Theme.of(context).textTheme.headline3, ), Space( height: 5, ), Text( "最近游玩:${sum.lastPlayAt}", style: Theme.of(context).textTheme.bodyText1, ) ], ) ], ); } Widget _buildWrapListTileWidget(String key, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( key, style: Theme.of(context).textTheme.subtitle1, ), Text( value, style: Theme.of(context).textTheme.subtitle1, ) ], ), ); } int toType() { return _TABS.indexOf(_tab.value); } Future changeTab(DateTime time) async { int type = toType(); 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), recordsToday: simple.data.records); } } return null; } void _showFirstTarget() async { SportTarget sportTarget; var result = await showDialog( context: context, barrierDismissible: false, builder: (context) => NotificationListener( onNotification: (notification) { sportTarget = notification.target; return true; }, child: CustomAlertDialog( addClose: true, title: '运动目标设置', child: Column( children: [ Align( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.only(left: 20.0), child: Text( "请选择每日运动目标", style: Theme.of(context).textTheme.subtitle1, ), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 5.0), child: TargetModify( editCustom: false, padding: false, ), ) ], ), ok: () async { if (sportTarget == null) { return; } RespData resp = (sportTarget.name == '自定义' ? await api.setSportTargetCustom(sportTarget.duration, sportTarget.consume) : await api.setSportTarget(sportTarget.id)); if (resp.code == 0) { Navigator.of(context).pop(resp.data); } else { ToastUtil.show(resp.msg); } }, ))); if (result is SportTargetToday) { if (result != null) { setState(() { _valueNotifierTarget.value = result; }); Provider.of(context, listen: false).durationTarget.value = result.target.duration; } } } } class _ProgressBar extends CustomPainter { final Paint _paint = Paint() ..color = Color(0xffeeeeee) ..isAntiAlias = true; final Paint _indicatorPaint = Paint() ..color = Color(0xffFFC400) ..isAntiAlias = true; double _paddingBar = 2; double percent; _ProgressBar(this.percent); @override void paint(Canvas canvas, Size size) { double indicator = size.width * min(1.0, this.percent); canvas.save(); var rect = Rect.fromLTRB(0, 0, size.width, size.height); canvas.clipRRect(RRect.fromRectAndRadius(rect, Radius.circular(size.height / 2)), doAntiAlias: true); canvas.drawRect(rect, _paint); Paint _valuePaint = Paint() ..shader = LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [Color(0xffFFE600), Color(0xffFF9100)], ).createShader(rect); canvas.drawRect(Rect.fromLTRB(0, 0, size.width * this.percent, size.height), _valuePaint); canvas.restore(); // Path path = Path() // ..moveTo(indicator, size.height - 13 - _paddingBar) // ..lineTo(indicator - 5, 0) // ..lineTo(indicator + 5, 0) // ..close(); // canvas.drawPath(path, _indicatorPaint); } @override bool shouldRepaint(CustomPainter oldDelegate) { return false; } }