import 'dart:async'; import 'package:fast/model/model.dart'; import 'package:fast/utils/api.dart'; import 'package:fast/utils/global.dart'; import 'package:fast/utils/http_utils.dart'; import 'package:fast/utils/size_fit.dart'; import 'package:fast/utils/util.dart'; import 'package:fast/view/component/challenge_result.dart'; import 'package:fast/view/component/fast.dart'; import 'package:fast/view/component/fast_btn.dart'; import 'package:fast/view/component/toast.dart'; import 'package:flutter/material.dart'; // ignore: must_be_immutable GlobalKey checkinKey = GlobalKey(); // ignore: must_be_immutable class FastCheckout extends StatefulWidget { bool abandon; bool supertimeout; int timestamp; Map checkInfo; FastBean fast; FastCheckout( {Key? key, required this.abandon, required this.supertimeout, required this.fast, required this.timestamp, required this.checkInfo}) : super(key: key); @override State createState() => _FastCheckoutState(); } class _FastCheckoutState extends State with TickerProviderStateMixin { late AnimationController controller2, controller3, controller4; late Animation aniStep2; late AnimationController animationController; late Animation step0, step1, step2, step3, step4, step5, step6; int step = 0; //动画执行步骤 bool rightTime = false; FastResultBean? resultBean; List checkList = []; Map? users; bool btnEnable = true; int earnings = 0; int index = 1; int win = 0; //判断是否准时签到(开始断食签到和结束断食签到都准时才算准时) late DateTime date; String desc1 = '', desc2 = '', desc3 = ''; @override void initState() { super.initState(); date = serverTime(); controller2 = AnimationController( vsync: this, duration: const Duration(milliseconds: 300)); controller3 = AnimationController( vsync: this, duration: const Duration(milliseconds: 1200)); controller4 = AnimationController( vsync: this, duration: const Duration(milliseconds: 300)); animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 3000)); step0 = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animationController, curve: const Interval(0.0, 300.0 / 3000.0))); step1 = Tween(begin: 1.0, end: 1.1).animate(CurvedAnimation( parent: animationController, curve: const Interval(300.0 / 3000.0, 1500.0 / 3000.0))); step2 = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animationController, curve: const Interval(1500 / 3000, 1800.0 / 3000.0))); step3 = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animationController, curve: const Interval(1800 / 3000, 2100.0 / 3000.0))); step4 = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animationController, curve: const Interval(2100.0 / 3000, 2400.0 / 3000.0))); step5 = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animationController, curve: const Interval(2400.0 / 3000, 2700.0 / 3000.0))); step6 = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: controller4, curve: const Interval(0.0, 1.0))); step0.addListener(() { if (step0.value == 1.0) { setState(() { step = 1; }); } else {} }); step1.addListener(() { if (step1.value == 1.1) { setState(() { step = 2; }); } }); animationController.addListener(() { setState(() {}); }); animationController.forward(); aniStep2 = Tween(begin: 1.0, end: 0.0).animate(controller2); controller2.addListener(() { setState(() {}); }); controller3.addListener(() { setState(() {}); }); controller4.addListener(() { setState(() {}); }); Timer(const Duration(milliseconds: 2000), () { setState(() { step = 1; controller2.forward(); }); }); getCheckinData(); getUsers(); if (widget.abandon) { giveUp(); } } void getCheckinData() { Map data = widget.checkInfo; setState(() { rightTime = data['checkout_status'] == 'SUCCESS'; win = data['checkout_rjv']; if (data['checkout_status'] == 'SUCCESS') { desc1 = '第${data['current_day_index']}天结束断食'; desc2 = Util().checkFormateDate( DateTime.fromMillisecondsSinceEpoch( (data['start_time'] as int) * 1000), serverTime()) + '~' + Util().checkFormateDate( DateTime.fromMillisecondsSinceEpoch( (data['end_time'] as int) * 1000), serverTime()) + ',已断食' + Util().betweenTime( DateTime.fromMillisecondsSinceEpoch( (data['start_time'] as int) * 1000), DateTime.fromMillisecondsSinceEpoch( (data['end_time'] as int) * 1000)); desc3 = Util().checkFormateDate( DateTime.fromMillisecondsSinceEpoch( (data['checkout_expire'] as int) * 1000), serverTime()) + '前完成打卡,视为准时打卡'; } else { desc1 = '第${data['current_day_index']}天结束断食'; desc2 = Util().checkFormateDate( DateTime.fromMillisecondsSinceEpoch( (data['start_time'] as int) * 1000), serverTime()) + '~' + Util().checkFormateDate( DateTime.fromMillisecondsSinceEpoch( (data['end_time'] as int) * 1000), serverTime()) + ',已断食' + Util().betweenTime( DateTime.fromMillisecondsSinceEpoch( (data['start_time'] as int) * 1000), DateTime.fromMillisecondsSinceEpoch( (data['end_time'] as int) * 1000)); desc3 = Util().checkFormateDate( DateTime.fromMillisecondsSinceEpoch( (data['checkout_invalid'] as int) * 1000), serverTime()) + '前完成打卡,视为超时打卡'; } if (widget.supertimeout) { desc1 = '第${data['current_day_index']}天结束断食·打卡失效\n挑战终止,欢迎再战'; desc3 = Util().checkFormateDate( DateTime.fromMillisecondsSinceEpoch( (data['checkout_invalid'] as int) * 1000), serverTime()) + '前未完成打卡,打卡已失效'; } }); List checkinDays = data['checkin_days']; List list = []; for (int i = 0; i < data['days']; i++) { ChallengeCheckinBean bean = ChallengeCheckinBean(); bean.day = i + 1; if (i < checkinDays.length && checkinDays[i]['checkout_status'] != 'PENDING') { bean.stone = checkinDays[i]['rjv_earnings']; bean.passed = true; } if (i == data['current_day_index'] - 1) { bean.today = true; if (widget.abandon || widget.supertimeout) { bean.abandon = true; } } list.add(bean); } List array = data['days'] == 7 ? [ [list[0], list[1], list[2], list[3]], [list[4], list[5], list[6]] ] : [list]; setState(() { checkList = array; index = data['current_day_index']; earnings = data['rjv_earnings']; }); } Future getUsers() async { Map data = await HttpUtils.get(Api.overalls); setState(() { users = data; }); } Future giveUp() async { Map data = await HttpUtils.post(Api.end, data: {'real_end_time': serverTime().millisecondsSinceEpoch ~/ 1000}); resultBean = FastResultBean.fromJson(data); } DateTime serverTime() { int milliseconds = DateTime.now().millisecondsSinceEpoch; milliseconds = milliseconds + Global().timeSeconds * 1000; return DateTime.fromMillisecondsSinceEpoch(milliseconds); } @override void dispose() { controller2.dispose(); controller3.dispose(); controller4.dispose(); animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { double screenHeight = MediaQuery.of(context).size.height; List widgets = []; for (int i = 0; i < earnings; i++) { widgets.add(Container( margin: EdgeInsets.only(left: 2.px), child: Image.asset( 'assets/images/stone.png', width: 12.px, height: 12.px, ), )); } double value1 = step == 0 ? step0.value : (1.0 - step2.value); double valueTop = step == 0 ? 0.14 * screenHeight + 0.1 * screenHeight * step0.value : 0.14 * screenHeight + 0.1 * screenHeight * (1 - step2.value); return Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage('assets/images/${widget.fast.days}days_bg.png'), fit: BoxFit.cover), ), alignment: Alignment.center, child: Column( children: [ Opacity( opacity: step0.value, child: Container( margin: EdgeInsets.only(top: valueTop), child: Column( children: [ Opacity( opacity: 0.4, child: Image.asset( 'assets/images/challenge_mode.png', width: 298.px, height: 28.px, ), ), SizedBox( height: 12.px, ), Image.asset( 'assets/images/challenge_${widget.fast.days}.png', width: 207.px, height: 96.px, ) ], ), ), ), Opacity( opacity: step3.value, child: Container( margin: EdgeInsets.only(top: 20.px * step3.value), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: List.generate( checkList.length, (index) { return Container( height: 50.px, width: checkList[index].length * 50.px, margin: EdgeInsets.only(bottom: 8.px), decoration: BoxDecoration( color: const Color.fromARGB(38, 196, 204, 218), borderRadius: BorderRadius.all(Radius.circular(25.px))), child: Row( children: List.generate( checkList[index].length, (j) { return CheckinItem( key: checkList[index][j].today ? checkinKey : null, bean: checkList[index][j], isCheckin: false, ); }), ), ); }, ), ))), Opacity( opacity: step4.value, child: Container( alignment: Alignment.center, // margin: EdgeInsets.only(top: 24.px * aniDesc.value), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( height: 12.px * step4.value, ), if (widget.abandon) Text( '挑战终止,欢迎再战', style: TextStyle( decoration: TextDecoration.none, color: const Color(0xFFFF0000), fontSize: 14.px), ), if (widget.supertimeout) Text( desc1, textAlign: TextAlign.center, style: TextStyle( decoration: TextDecoration.none, color: const Color(0xFFFF0000), fontSize: 14.px), ), if (!widget.abandon && !widget.supertimeout) Column( children: [ Text( desc1, style: TextStyle( color: const Color(0xFFC4CCDA), fontSize: 16.px), ), SizedBox( height: 2.px, ), Text( desc2, style: TextStyle( color: const Color(0x99C4CCDA), fontSize: 12.px), ) ], ) ], ), ), ), const Expanded(child: SizedBox()), if (users != null) Opacity( opacity: step6.value, child: Stack( alignment: AlignmentDirectional.topStart, children: [ bottomAvatar(4, const Color(0xCC080C1A)), bottomAvatar(3, const Color(0x99080C1A)), bottomAvatar(2, const Color(0x66080C1A)), bottomAvatar(1, const Color(0x33080C1A)), bottomAvatar(0, const Color(0x00080C1A)), Container( margin: EdgeInsets.fromLTRB( 12 * 4.px + 24.px + 8.px, 1.px, 0, 0), height: 16.px, child: Text( users!['fasting_count'].toString() + '人次已完成挑战', style: TextStyle( color: const Color(0x66FFFFFF), fontSize: 14.px, ), ), ) ], ), ), SizedBox( height: 24.px, ), Opacity( opacity: step4.value, child: Column( children: [ if (widget.supertimeout || widget.abandon) FastBtn( title: '确定', disable: btnEnable ? false : true, width: 228.px, height: 48.px, callback: () { if (Global().allowNotification == false && Global().pushEnable) { Util().showNotificationStatus(context); return; } touchBtn(); }), if (!widget.supertimeout && !widget.abandon) FastBtn( title: '确定', img: Image.asset( 'assets/images/checkout.png', width: 132.px, height: 26.px, ), disable: btnEnable ? false : true, width: 228.px, height: 48.px, callback: () { if (Global().allowNotification == false && Global().pushEnable) { Util().showNotificationStatus(context); return; } touchBtn(); }), SizedBox( height: 17.px, ), if (!widget.abandon) Text( desc3, style: TextStyle( color: const Color(0x99C4CCDA), fontSize: 12.px), ), SizedBox( height: 0.07 * screenHeight * step4.value, ) ], ), ), ], ), ); } bottomAvatar(int index, Color color) { if (users!['recent_users'].length > index) { return Container( width: 24.px, height: 24.px, margin: EdgeInsets.fromLTRB(index * 12.px, 0, 0, 0), child: Stack( children: [ ClipOval( child: Image.network( users!['recent_users'][index]['avatar'], width: 24.px, height: 24.px, fit: BoxFit.cover, ), ), Container( width: 24.px, height: 24.px, decoration: BoxDecoration( color: color, shape: BoxShape.circle, border: Border.all(color: const Color(0xFF000D26))), ) ], ), ); } return SizedBox( width: 0.px, height: 0.px, ); } touchBtn() { if (widget.abandon) { endConfirm(); } else { // checkinKey.currentState!.checkin(rightTime); if (widget.supertimeout) { superTimeoutEnd(); } else { checkout(); } setState(() { btnEnable = false; }); } } Future endConfirm() async { await HttpUtils.post(Api.endConfirm, data: {}); showResultPage(); } Future superTimeoutEnd() async { Map data = await HttpUtils.post(Api.checkout, data: {'day_index': index, 'checkout_time': widget.timestamp}); resultBean = FastResultBean.fromJson(data); showResultPage(); } Future checkout() async { Map data = await HttpUtils.post(Api.checkout, data: {'day_index': index, 'checkout_time': widget.timestamp}); controller4.forward(); resultBean = FastResultBean.fromJson(data); if (widget.supertimeout) { showResultPage(); return; } checkinKey.currentState!.checkout(rightTime, win); if (resultBean!.over!) { Timer(const Duration(milliseconds: 2500), () { showResultPage(); }); } else { Timer(const Duration(milliseconds: 2500), () { Navigator.of(context).pop(); Global().homePage.closeCheckPop(); if (rightTime) { //正常打卡 showDialog( context: context, barrierDismissible: false, barrierColor: Colors.transparent, builder: (BuildContext context) { return Toast( title: '准时打卡', content: win > 0 ? Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( '+$win', style: TextStyle( color: Colors.white, fontSize: 16.px, fontWeight: FontWeight.w800, fontFamily: 'Exo2', decoration: TextDecoration.none), ), SizedBox( width: 3.px, ), Image.asset( 'assets/images/stone.png', width: 24.px, height: 24.px, ) ], ) : const SizedBox( width: 0, ), ); }); } else { showDialog( context: context, barrierDismissible: false, barrierColor: Colors.transparent, builder: (BuildContext context) { return Toast( title: '超时打卡', content: const SizedBox( width: 0, ), ); }); //超时打卡 } }); Global().homePage.getDatas(); if (Global().fastKey!.currentContext != null) { FastState seekBar = Global().fastKey!.currentState as FastState; seekBar.reset(); } } } showResultPage() { Navigator.of(context).pop(); Global().homePage.closeCheckPop(); showDialog( context: context, barrierDismissible: false, barrierColor: Colors.transparent, useSafeArea: false, builder: (BuildContext context) { return ChallengeResult(resultBean: resultBean!); }); } } // ignore: must_be_immutable class CheckinItem extends StatefulWidget { ChallengeCheckinBean bean; bool isCheckin; CheckinItem({Key? key, required this.bean, required this.isCheckin}) : super(key: key); @override State createState() => CheckinItemState(); } class CheckinItemState extends State with SingleTickerProviderStateMixin { late AnimationController controller; late Animation scaleAni, scaleAni2; bool isRightTime = false; bool ing = false; @override void initState() { controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 1600)); TweenSequenceItem item0 = TweenSequenceItem(tween: Tween(begin: 0.0, end: 1.5), weight: 2); TweenSequenceItem item1 = TweenSequenceItem(tween: Tween(begin: 1.5, end: 1.4), weight: 1); TweenSequenceItem item2 = TweenSequenceItem(tween: Tween(begin: 1.45, end: 1.45), weight: 20); TweenSequenceItem item3 = TweenSequenceItem(tween: Tween(begin: 1.45, end: 1.0), weight: 1); TweenSequence tweenSequence = TweenSequence([item0, item1, item2, item3]); scaleAni = tweenSequence.animate( CurvedAnimation(parent: controller, curve: const Interval(0.0, 0.95))); TweenSequenceItem a0 = TweenSequenceItem(tween: Tween(begin: 1.0, end: 0.9), weight: 1); TweenSequenceItem a1 = TweenSequenceItem(tween: Tween(begin: 0.9, end: 1.0), weight: 1); TweenSequence tween2 = TweenSequence([a0, a1]); scaleAni2 = tween2.animate( CurvedAnimation(parent: controller, curve: const Interval(0.9, 1.0))); controller.addListener(() { setState(() {}); }); // Timer(const Duration(seconds: 3), () { // if (widget.isToday) { // controller.forward(); // } // }); super.initState(); } checkin(bool rightTime) { setState(() { if (rightTime) { isRightTime = true; } else { isRightTime = false; } // isRightTime = rightTime; if (widget.isCheckin) { ing = true; } }); if (widget.bean.today) { controller.forward(); } } checkout(bool rightTime, int win) { setState(() { if (rightTime && win > 0) { isRightTime = true; } else { isRightTime = false; } // isRightTime = rightTime; if (widget.isCheckin) { ing = true; } }); if (widget.bean.today) { controller.forward(); } } @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { String iconPath = 'assets/images/checkin_undone.png'; String day = widget.bean.day.toString(); if (widget.bean.abandon) { iconPath = 'assets/images/checkin_fail.png'; } else { if (widget.bean.passed) { iconPath = 'assets/images/checkin_done.png'; } else if (widget.bean.today) { iconPath = widget.isCheckin ? 'assets/images/checkin_undone.png' : 'assets/images/checkin_today.png'; } else { iconPath = 'assets/images/checkin_undone.png'; } } String today = ''; if (ing) { today = day; day = ''; } if (widget.bean.passed || (widget.bean.abandon && widget.bean.today)) { day = ''; } double opacity = (widget.bean.today && !widget.isCheckin) ? 1.0 : 0.5; if (ing) { opacity = 1.0; } return Opacity( opacity: opacity, child: Stack( children: [ Opacity( opacity: ing ? 0.0 : 1.0, child: Container( width: 50.px, height: 50.px, alignment: Alignment.center, decoration: BoxDecoration( image: DecorationImage( image: AssetImage(iconPath), fit: BoxFit.cover)), child: Text( day, style: TextStyle( color: (widget.bean.today && !widget.isCheckin) ? Colors.white : const Color(0x66C4CCDA), fontFamily: 'Exo2', fontWeight: FontWeight.w600, fontSize: 16.px, decoration: TextDecoration.none), ), ), ), if (widget.bean.passed && widget.bean.stone > 0) Positioned( bottom: 5.px, left: 11.px, child: Container( width: 28.px, height: 14.px, alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(7.px)), color: const Color(0xFF050F1A)), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( '+' + widget.bean.stone.toString(), style: TextStyle( color: Colors.white, fontSize: 8.px, fontFamily: 'Exo2', decoration: TextDecoration.none), ), Image.asset( 'assets/images/stone.png', width: 10.px, height: 10.px, ) ], ), )), Transform.scale( scale: scaleAni.value, child: Container( width: 50.px, height: 50.px, alignment: widget.isCheckin ? Alignment.center : Alignment.bottomCenter, decoration: BoxDecoration( image: DecorationImage( image: widget.isCheckin ? const AssetImage( 'assets/images/checkin_today.png') : const AssetImage( 'assets/images/checked_today.png'), fit: BoxFit.cover)), child: widget.isCheckin ? Text( today, style: TextStyle( color: Colors.white, fontFamily: 'Exo2', fontWeight: FontWeight.w600, fontSize: 16.px), ) : !isRightTime ? const SizedBox( width: 0, ) : Container( width: 28.px, height: 14.px, alignment: Alignment.center, margin: EdgeInsets.only(bottom: 4.px), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(7.px)), color: const Color(0xFF050F1A)), child: Transform.scale( scale: scaleAni2.value, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( '+1', style: TextStyle( color: Colors.white, fontSize: 8.px, fontFamily: 'Exo2', decoration: TextDecoration.none), ), Image.asset( 'assets/images/stone.png', width: 10.px, height: 10.px, ) ], )), ), ), ) ], )); } }