eat.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. import 'dart:async';
  2. import 'package:fast/extension/button.dart';
  3. import 'package:fast/extension/relation.dart';
  4. import 'package:fast/model/model.dart';
  5. import 'package:fast/utils/global.dart';
  6. import 'package:fast/utils/size_fit.dart';
  7. import 'package:fast/utils/util.dart';
  8. import 'package:flutter/material.dart';
  9. import 'alert_widget.dart';
  10. import 'bubble_widget.dart';
  11. import 'challenge_checkout.dart';
  12. class Eat extends StatefulWidget {
  13. const Eat({Key? key}) : super(key: key);
  14. @override
  15. _EatState createState() => _EatState();
  16. }
  17. class _EatState extends State<Eat> {
  18. final GlobalKey globalKey = GlobalKey();
  19. FastBean currentFast = Global().currentFast!;
  20. late Timer timer;
  21. double x = 38.px;
  22. int s = 6;
  23. @override
  24. void initState() {
  25. super.initState();
  26. WidgetsBinding.instance?.addPostFrameCallback((_) {
  27. setState(() {
  28. var obj = globalKey.currentContext;
  29. var width = obj?.size!.width;
  30. x = (width! / 2 + 17.px) * 2;
  31. });
  32. });
  33. timer = Timer.periodic(const Duration(seconds: 1), (e) {
  34. setState(() {
  35. s--;
  36. if (s == 0) {
  37. timer.cancel();
  38. }
  39. });
  40. });
  41. }
  42. @override
  43. void didChangeDependencies() {
  44. super.didChangeDependencies();
  45. }
  46. @override
  47. void dispose() {
  48. timer.cancel();
  49. super.dispose();
  50. }
  51. @override
  52. Widget build(BuildContext context) {
  53. int fastTime = (currentFast.endTime! - currentFast.startTime!) ~/ 60;
  54. int fastHour = fastTime ~/ 60;
  55. int fastMinute = fastTime % 60.toInt();
  56. int eatMinute = fastMinute == 0 ? 0 : 60 - fastMinute;
  57. int eatHour = fastMinute == 0 ? 24 - fastHour : 23 - fastHour;
  58. String strFast = '';
  59. String strEat = '';
  60. if (fastMinute == 0) {
  61. strFast = fastHour.toString();
  62. strEat = eatHour.toString();
  63. } else {
  64. strFast =
  65. fastHour.toString() + ':' + fastMinute.toString().padLeft(2, '0');
  66. strEat = eatHour.toString() + ':' + eatMinute.toString().padLeft(2, '0');
  67. }
  68. Size sz = Util().boundingTextSize(
  69. strEat, TextStyle(fontFamily: 'Exo2', fontSize: 32.px));
  70. return Stack(children: [
  71. Positioned(top: 10.px, right: 0.px, child: const Progress()),
  72. Column(mainAxisAlignment: MainAxisAlignment.start, children: [
  73. SizedBox(
  74. height: 30.px,
  75. ),
  76. const Bubble(),
  77. SizedBox(
  78. height: 30.px,
  79. ),
  80. if (s > 0)
  81. Row(mainAxisAlignment: MainAxisAlignment.center, children: [
  82. Expanded(
  83. child: Text(
  84. strFast,
  85. textAlign: TextAlign.right,
  86. style: TextStyle(
  87. color: const Color(0x99FFFFFF),
  88. fontFamily: 'Exo2',
  89. fontSize: 32.px),
  90. ),
  91. ),
  92. SizedBox(
  93. width: 12.px,
  94. ),
  95. Text('/',
  96. style: TextStyle(
  97. color: const Color(0x99FFFFFF),
  98. fontFamily: 'Exo2',
  99. fontSize: 16.px)),
  100. SizedBox(
  101. width: 12.px,
  102. ),
  103. Expanded(
  104. child: Row(
  105. children: [
  106. if (s > 2)
  107. Text(
  108. strEat,
  109. key: globalKey,
  110. style: TextStyle(
  111. color: const Color(0xFFFF9B00),
  112. fontFamily: 'Exo2',
  113. fontWeight: FontWeight.w600,
  114. fontSize: 32.px),
  115. ).relationOne(
  116. -49.px + sz.width / 2.0,
  117. -40,
  118. BubbleWidget(
  119. 94.px,
  120. 42.px,
  121. const Color(0xFFFF9B00),
  122. BubbleArrowDirection.bottom,
  123. arrAngle: 80.px,
  124. arrHeight: 8.px,
  125. child: Text(
  126. '进食开始…',
  127. style: TextStyle(
  128. color: Colors.black,
  129. fontSize: 14.px,
  130. fontWeight: FontWeight.bold),
  131. ),
  132. )),
  133. if (s <= 2)
  134. Container(
  135. margin: EdgeInsets.only(bottom: 4.px),
  136. child: Text(
  137. strEat,
  138. key: globalKey,
  139. style: TextStyle(
  140. color: const Color(0xFFFF9B00),
  141. fontFamily: 'Exo2',
  142. fontWeight: FontWeight.w600,
  143. fontSize: 32.px),
  144. ),)
  145. ],
  146. ))
  147. ]),
  148. if (s <= 0)
  149. Container(
  150. alignment: Alignment.center,
  151. child: RRectButton(
  152. '结束挑战',
  153. width: 144.px,
  154. height: 50.px,
  155. backgroundColor: const Color(0x1AC4CCDA),
  156. textColor: const Color(0xFFC4CCDA),
  157. radius: 25.px,
  158. fontSize: 16.px,
  159. onPressed: () => end(),
  160. ),
  161. )
  162. ])
  163. ]);
  164. }
  165. end() {
  166. if (Global().allowNotification == false && Global().pushEnable) {
  167. Util().showNotificationStatus(context);
  168. return;
  169. }
  170. showConfirm('确定现在退出\n"${currentFast.days}天连续计划"吗?', () {
  171. Global().homePage.showCheckout(true);
  172. });
  173. }
  174. void showConfirm(String title, Function() confirmCallback) {
  175. showDialog(
  176. context: context,
  177. barrierDismissible: false,
  178. barrierColor: const Color(0xF2000D1F),
  179. builder: (BuildContext context) {
  180. return AlertWidget(
  181. title: title,
  182. confirm: '确定',
  183. confirmCallback: () {
  184. Navigator.of(context).pop();
  185. confirmCallback();
  186. });
  187. });
  188. }
  189. }
  190. class Progress extends StatefulWidget {
  191. const Progress({Key? key}) : super(key: key);
  192. @override
  193. _ProgressState createState() => _ProgressState();
  194. }
  195. class _ProgressState extends State<Progress> {
  196. late FastBean fastBean;
  197. @override
  198. void initState() {
  199. fastBean = Global().currentFast!;
  200. super.initState();
  201. }
  202. @override
  203. Widget build(BuildContext context) {
  204. return Container(
  205. width: 64.px,
  206. height: 54.px,
  207. alignment: Alignment.center,
  208. margin: EdgeInsets.fromLTRB(0, 0, 14.px, 0.px),
  209. decoration: BoxDecoration(
  210. color: const Color(0x1AFFFFFF),
  211. borderRadius: BorderRadius.all(Radius.circular(12.px))),
  212. child: Column(
  213. mainAxisAlignment: MainAxisAlignment.start,
  214. children: [
  215. SizedBox(height: 5.px,),
  216. Container(
  217. padding: EdgeInsets.fromLTRB(0, 0, 0, 6.px),
  218. decoration: BoxDecoration(
  219. border: Border(
  220. bottom: BorderSide(
  221. color: const Color(0x1AFFFFFF), width: 1.px))),
  222. child: Text(
  223. '${fastBean.days}天挑战',
  224. style:
  225. TextStyle(color: const Color(0x66FFFFFF), fontSize: 10.px),
  226. ),
  227. ),
  228. SizedBox(
  229. height: 2.px,
  230. ),
  231. Row(
  232. mainAxisAlignment: MainAxisAlignment.center,
  233. crossAxisAlignment: CrossAxisAlignment.center,
  234. children: [
  235. Text(
  236. '${fastBean.finishDays}',
  237. style: TextStyle(
  238. color: const Color(0xFFC4CCDA),
  239. fontSize: 16.px,
  240. fontWeight: FontWeight.bold,
  241. fontFamily: 'Exo2'),
  242. ),
  243. const SizedBox(
  244. width: 3,
  245. ),
  246. Text(
  247. '/',
  248. style:
  249. TextStyle(color: const Color(0xFFC4CCDA), fontSize: 9.px),
  250. ),
  251. const SizedBox(
  252. width: 3,
  253. ),
  254. Text(
  255. '${fastBean.days}',
  256. style: TextStyle(
  257. color: const Color(0xFFC4CCDA),
  258. fontSize: 16.px,
  259. fontWeight: FontWeight.bold,
  260. fontFamily: 'Exo2'),
  261. )
  262. ],
  263. )
  264. ],
  265. ));
  266. }
  267. }
  268. class Bubble extends StatelessWidget {
  269. const Bubble({Key? key}) : super(key: key);
  270. @override
  271. Widget build(BuildContext context) {
  272. return Stack(children: [
  273. BubbleRotation(
  274. key: UniqueKey(),
  275. duration: 3000,
  276. alignment: Alignment.topLeft,
  277. padding: EdgeInsets.zero,
  278. ),
  279. BubbleRotation(
  280. key: UniqueKey(),
  281. duration: 2500,
  282. alignment: Alignment.topRight,
  283. padding: EdgeInsets.all(6.px),
  284. ),
  285. const BubbleContent(),
  286. ]);
  287. }
  288. }
  289. class BubbleContent extends StatefulWidget {
  290. const BubbleContent({Key? key}) : super(key: key);
  291. @override
  292. State<BubbleContent> createState() => _BubbleContentState();
  293. }
  294. class _BubbleContentState extends State<BubbleContent> {
  295. late FastBean fastBean;
  296. late Timer timer;
  297. late String hourMinute;
  298. late String second;
  299. int type = 0;
  300. @override
  301. void initState() {
  302. calulate();
  303. timer = Timer.periodic(const Duration(seconds: 1), (t) {
  304. calulate();
  305. });
  306. super.initState();
  307. }
  308. @override
  309. void dispose() {
  310. timer.cancel();
  311. super.dispose();
  312. }
  313. void calulate() {
  314. fastBean = Global().currentFast!;
  315. int seconds =
  316. fastBean.startTime! - serverTime().millisecondsSinceEpoch ~/ 1000;
  317. int hours = seconds ~/ 3600;
  318. int minutes = seconds % 3600 ~/ 60;
  319. int sec = (seconds % 60).floor();
  320. // seconds = 8;
  321. setState(() {
  322. if (seconds >= 60) {
  323. type = 0;
  324. hourMinute = hours.toString().padLeft(2, '0') +
  325. ':' +
  326. minutes.toString().padLeft(2, '0');
  327. second = sec.toString().padLeft(2, '0');
  328. } else if (seconds >= 10) {
  329. type = 1;
  330. second = sec.toString();
  331. } else {
  332. type = 2;
  333. second = sec.toString();
  334. if (sec==0){
  335. Global().homePage.eatEnd();
  336. }
  337. }
  338. });
  339. }
  340. DateTime serverTime() {
  341. int milliseconds = DateTime.now().millisecondsSinceEpoch;
  342. milliseconds = milliseconds + Global().timeSeconds * 1000;
  343. return DateTime.fromMillisecondsSinceEpoch(milliseconds);
  344. }
  345. @override
  346. Widget build(BuildContext context) {
  347. return Container(
  348. width: 300.px,
  349. height: 300.px,
  350. alignment: Alignment.center,
  351. child: Container(
  352. width: 260.px,
  353. height: 260.px,
  354. alignment: Alignment.center,
  355. decoration: BoxDecoration(
  356. borderRadius: BorderRadius.all(Radius.circular(260.px)),
  357. color: const Color(0xffFF9B00)),
  358. child: Column(
  359. children: [
  360. if (type != 2)
  361. Container(
  362. margin: EdgeInsets.only(top: type == 0 ? 57.px : 76.px),
  363. child: Text(
  364. '距下次断食开始还剩',
  365. style: TextStyle(
  366. color: Colors.white, fontSize: 12.px, height: 1.0),
  367. ),
  368. ),
  369. if (type == 0)
  370. Container(
  371. margin: EdgeInsets.only(top: 6.px),
  372. foregroundDecoration: const BoxDecoration(
  373. gradient: LinearGradient(
  374. begin: Alignment.topCenter,
  375. end: Alignment.bottomCenter,
  376. colors: [Color(0x80FF9B00), Color(0x00FF9B00)])),
  377. child: Text(
  378. hourMinute,
  379. style: TextStyle(
  380. color: Colors.white,
  381. fontFamily: 'Fast',
  382. height: 1.0,
  383. fontSize: 40.px),
  384. ),
  385. ),
  386. Container(
  387. margin: EdgeInsets.only(top: type==0?0.0:type==2?80.px:10.px),
  388. child: Text(
  389. second,
  390. style: TextStyle(
  391. color: Colors.white,
  392. fontFamily: 'Fast',
  393. height: 1.0,
  394. fontSize: type == 2 ? 120.px : 80.px),
  395. ),)
  396. ],
  397. ),
  398. ));
  399. }
  400. }
  401. // ignore: must_be_immutable
  402. class BubbleRotation extends StatefulWidget {
  403. int duration;
  404. AlignmentGeometry alignment;
  405. EdgeInsets padding;
  406. BubbleRotation(
  407. {Key? key,
  408. required this.duration,
  409. required this.alignment,
  410. required this.padding})
  411. : super(key: key);
  412. @override
  413. _BubbleRotationState createState() => _BubbleRotationState();
  414. }
  415. class _BubbleRotationState extends State<BubbleRotation>
  416. with SingleTickerProviderStateMixin {
  417. AnimationController? controller;
  418. @override
  419. void initState() {
  420. super.initState();
  421. controller = AnimationController(
  422. vsync: this, duration: Duration(milliseconds: widget.duration));
  423. controller!.addStatusListener((status) {
  424. if (status == AnimationStatus.completed) {
  425. controller!.reset();
  426. controller!.forward();
  427. } else if (status == AnimationStatus.dismissed) {}
  428. });
  429. controller!.forward();
  430. }
  431. @override
  432. void dispose() {
  433. controller!.dispose();
  434. super.dispose();
  435. }
  436. @override
  437. Widget build(BuildContext context) {
  438. return RotationTransition(
  439. turns: controller!,
  440. alignment: Alignment.center,
  441. child: Container(
  442. width: 300.px,
  443. height: 300.px,
  444. alignment: widget.alignment,
  445. padding: widget.padding,
  446. child: Container(
  447. width: 260.px,
  448. height: 260.px,
  449. decoration: BoxDecoration(
  450. borderRadius: BorderRadius.all(Radius.circular(260.px)),
  451. color: const Color(0x75FF9B00))),
  452. ),
  453. );
  454. }
  455. }