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/fast_btn.dart'; import 'package:fast/view/component/toast.dart'; import 'package:flutter/material.dart'; import 'challenge_checkout.dart'; import 'challenge_result.dart'; // ignore: must_be_immutable GlobalKey checkinKey2 = GlobalKey(); // ignore: must_be_immutable class FastCheckin extends StatefulWidget { bool abandon; bool supertimeout; FastBean fast; int timestamp; Map checkInfo; FastCheckin( {Key? key, required this.abandon, required this.supertimeout, required this.fast, required this.timestamp, required this.checkInfo}) : super(key: key); @override State createState() => _FastCheckinState(); } class _FastCheckinState extends State with TickerProviderStateMixin { late AnimationController controller2, controller3; late Animation aniStep2; late AnimationController animationController; late Animation step0, step1, step2, step3, step4, step5; bool rightTime = false; FastResultBean? resultBean; String desc1 = '', desc2 = '', desc3 = ''; // Key key = // ObjectKey checkinKey = const ObjectKey('item'); // LocalKey key = LocalKey(); List checkList = []; bool btnEnable = true; int earnings = 0; int index = 1; int step = 0; //动画执行步骤 late DateTime date; @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)); 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))); 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(); /* 动画步骤 step 0: 状态图片从上到下 alpha 0-1 step 1: 进食时长从下到上 alpha 0-1 step 2: 页面停留 1s step 3: 状态图片从下到上推一点 alpha 保持不变 进食时长从上到下 alpha 1-0 step 4: 打卡从上到下 alpha 0-1 step 5: 描述 从上到下 alpha 0-1 step 4-5: 底部按钮 alpha 0-1 */ aniStep2 = Tween(begin: 1.0, end: 0.0).animate(controller2); controller2.addListener(() { setState(() {}); }); controller3.addListener(() { setState(() {}); }); Timer(const Duration(milliseconds: 2000), () { setState(() { step = 1; controller2.forward(); }); }); getCheckinData(); if (widget.abandon) { giveUp(); } } DateTime serverTime() { int milliseconds = DateTime.now().millisecondsSinceEpoch; milliseconds = milliseconds + Global().timeSeconds * 1000; return DateTime.fromMillisecondsSinceEpoch(milliseconds); } Future giveUp() async { Map data = await HttpUtils.post(Api.end, data: {'real_end_time': serverTime().millisecondsSinceEpoch ~/ 1000}); resultBean = FastResultBean.fromJson(data); } void getCheckinData() { Map data = widget.checkInfo; setState(() { rightTime = data['checkin_status'] == 'SUCCESS'; if (data['checkin_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['checkin_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['checkin_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]['checkin_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']; }); } @override void dispose() { Global().showCheckin = false; controller2.dispose(); controller3.dispose(); animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { SizeFit.initialize(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); int eatTime = 24 * 3600 - (widget.fast.endTime! - widget.fast.startTime!); int eatHour = eatTime ~/ 3600; int eatMinute = eatTime % 3600 ~/ 60; String str = eatMinute > 0 ? eatMinute.toString() + '分钟' : ''; String strEatTime = eatHour.toString() + '小时' + str; 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, ) ], ), ), ), Stack( children: [ // Opacity( // opacity: value1, // child: Container( // alignment: Alignment.center, // child: Column( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // SizedBox( // height: 8.px + (1 - value1) * 200.px, // ), // Text( // '第${widget.fast.currentDayIndex}天进食结束', // textAlign: TextAlign.center, // style: TextStyle( // color: const Color(0xFFFF9B00), // fontSize: 36.px, // fontWeight: FontWeight.bold), // ), // SizedBox(height: 12.px), // Text('进食时长$strEatTime', // textAlign: TextAlign.center, // style: // TextStyle(color: Colors.white, fontSize: 16.px)), // ], // ), // ), // ), Opacity( opacity: step4.value, child: Container( alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( height: checkList.length == 1 ? 95.px : 120.px + 20.px * step4.value, ), Text( desc1, // '第 ${widget.fast.currentDayIndex} 天开始断食打卡签到准时', textAlign: TextAlign.center, style: TextStyle( color: (widget.abandon || widget.supertimeout) ? const Color(0xFFFF0000) : const Color(0xFFC4CCDA), fontSize: (widget.abandon || widget.supertimeout) ? 12.px : 13.px), ), SizedBox( height: 5.px, ), Text( (widget.abandon || widget.supertimeout) ? '' : desc2, // '每晚餐后开始断食打卡,次日早餐前\n结束断食打卡,你已连续坚持 2 天', textAlign: TextAlign.center, style: TextStyle( color: const Color(0x99C4CCDA), fontSize: 12.px), ) ], ), ), ), Opacity( opacity: step3.value, child: Container( alignment: Alignment.center, margin: EdgeInsets.only(top: 12.px * step3.value), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.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 ? checkinKey2 : null, bean: checkList[index][j], isCheckin: true, ); }), ), ); }, ), ))), ], ), Expanded( child: SizedBox( height: 0.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; } if (widget.abandon) { endConfirm(); } else if (widget.supertimeout) { superTimeoutEnd(); } else { checkin(); // checkinKey.currentState!.checkin(); setState(() { btnEnable = false; }); } }), if (!widget.supertimeout && !widget.abandon) FastBtn( title: "", img: Image.asset( 'assets/images/checkin.png', width: 132.px, height: 26.px, ), disable: !btnEnable, width: 228.px, height: 48.px, callback: () { if (Global().allowNotification == false && Global().pushEnable) { Util().showNotificationStatus(context); return; } if (widget.abandon) { endConfirm(); } else if (widget.supertimeout) { superTimeoutEnd(); } else { checkin(); // checkinKey.currentState!.checkin(); setState(() { btnEnable = false; }); } }), SizedBox( height: 17.px, ), Text( desc3, //'「结束断食」打卡后,即可获得逆龄石', style: TextStyle( color: const Color(0x99C4CCDA), fontSize: 12.px), ), SizedBox( height: 0.07 * screenHeight * step4.value, ) ], ), ) ], ), ); } Future superTimeoutEnd() async { Map data = await HttpUtils.post(Api.checkin, data: {'day_index': index, 'checkin_time': widget.timestamp}); resultBean = FastResultBean.fromJson(data); showResultPage(); } Future endConfirm() async { await HttpUtils.post(Api.endConfirm, data: {}); showResultPage(); } 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!); }); } Future checkin() async { Map data = await HttpUtils.post(Api.checkin, data: {'day_index': index, 'checkin_time': widget.timestamp}); resultBean = FastResultBean.fromJson(data); checkinKey2.currentState!.checkin(false); if (resultBean!.over!) { Timer(const Duration(milliseconds: 2500), () { showResultPage(); }); } else { Timer(const Duration(milliseconds: 2500), () { Navigator.of(context).pop(); showDialog( context: context, barrierDismissible: false, barrierColor: Colors.transparent, builder: (BuildContext context) { return Toast( title: rightTime ? '准时打卡' : '超时打卡', content: const SizedBox( width: 0, ), ); }); Global().homePage.closeCheckPop(); if (widget.supertimeout) { showResultPage(); return; } else { //正常打卡 } }); Global().homePage.checkUpdate(); } } }