game_guide.dart 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024
  1. import 'dart:io';
  2. import 'dart:math';
  3. import 'package:animated_widgets/animated_widgets.dart';
  4. import 'package:cached_network_image/cached_network_image.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter/services.dart';
  7. import 'package:get_it/get_it.dart';
  8. import 'package:like_button/like_button.dart';
  9. import 'package:sport/application.dart';
  10. import 'package:sport/bean/game.dart';
  11. import 'package:sport/game/sdk_parse.dart';
  12. import 'package:sport/provider/bluetooth.dart';
  13. import 'package:sport/services/app_subscription_state.dart';
  14. import 'package:sport/widgets/dialog/alert_dialog.dart';
  15. import 'package:sport/widgets/dialog/game_alert_dialog.dart';
  16. import 'package:sport/widgets/image.dart';
  17. import 'package:video_player/video_player.dart';
  18. import 'package:visibility_detector/visibility_detector.dart';
  19. import 'package:wakelock/wakelock.dart';
  20. Map<int, List<Map<String, dynamic>>> guideActionMap = {
  21. 0: [
  22. {
  23. "name": "开始",
  24. "motion": [],
  25. "video": "http://static.ouj.com/video/b0e0ac59-e150-38d6-bd24-5bc1d83b0ece.mp4",
  26. },
  27. {
  28. "name": "右踏步",
  29. "motion": [4],
  30. "video": "http://static.ouj.com/video/9ef77bac-2a6f-3aaa-8e8c-b0e7b7916624.mp4",
  31. },
  32. {
  33. "name": "左踏步",
  34. "motion": [3],
  35. "video": "http://static.ouj.com/video/9b4706aa-3809-309e-84a4-040c498a2e15.mp4",
  36. },
  37. {
  38. "name": "右踮脚",
  39. "motion": [5],
  40. "video": "http://static.ouj.com/video/64fab916-00ac-33fa-b8d0-cebf4c077f26.mp4",
  41. },
  42. {
  43. "name": "左踮脚",
  44. "motion": [6],
  45. "video": "http://static.ouj.com/video/f1f49cfc-3fc5-37eb-a8ac-d8a1eee0bc11.mp4",
  46. },
  47. ],
  48. 3: [
  49. {
  50. "name": "开始",
  51. "motion": [],
  52. "video": "http://static.ouj.com/video/962d2d03-2a47-3418-bc1d-7b92afa29009.mp4",
  53. },
  54. {
  55. "name": "原地跑",
  56. "motion": [1],
  57. "motion_type": 1,
  58. "video": "http://static.ouj.com/video/0c067c1d-a8ac-3e1b-9b5a-dda32abbdb1d.mp4",
  59. },
  60. {
  61. "name": "左换道",
  62. "motion": [4],
  63. "video": "http://static.ouj.com/video/ee846e82-3829-3bf4-93ba-caf810aedb2b.mp4",
  64. },
  65. {
  66. "name": "右换道",
  67. "motion": [5],
  68. "video": "http://static.ouj.com/video/4ab6498e-a3fc-3870-af06-8aeba3f97453.mp4",
  69. },
  70. {
  71. "name": "跳跃",
  72. "motion": [2],
  73. "video": "http://static.ouj.com/video/299eb3d1-8d73-3ca1-80c2-8469ffc770fa.mp4",
  74. },
  75. {
  76. "name": "下蹲",
  77. "motion": [3],
  78. "video": "http://static.ouj.com/video/47155e50-a14a-329e-8b33-b9d5d101f184.mp4",
  79. },
  80. ],
  81. 1: [
  82. {
  83. "name": "开始",
  84. "motion": [],
  85. "video": "http://static.ouj.com/video/18d5f02d-b397-3c1d-8604-ffa84005ed09.mp4",
  86. },
  87. {
  88. "name": "原地踩",
  89. "motion": [12],
  90. "video": "http://static.ouj.com/video/ee05b628-63e6-30d1-9595-b1efbf71ebd8.mp4",
  91. "icon": "http://static.ouj.com/shoes/game/136e85575f574865a544a87c2ae24d9c.png",
  92. },
  93. {
  94. "name": "左上踩",
  95. "motion": [8],
  96. "video": "http://static.ouj.com/video/4f4424f6-3819-3f70-b1db-3cb68f0b0261.mp4",
  97. "icon": "http://static.ouj.com/shoes/game/4e55b8819c9f4b2dbbad64db3cf20605.png",
  98. },
  99. {
  100. "name": "左下踩",
  101. "motion": [9],
  102. "video": "http://static.ouj.com/video/f574233d-7580-35cb-81c5-0d29e716a83e.mp4",
  103. "icon": "http://static.ouj.com/shoes/game/b332785b77244bc59ea67b9b76ae11b3.png",
  104. },
  105. {
  106. "name": "右上踩",
  107. "motion": [10],
  108. "video": "http://static.ouj.com/video/c05c97fc-218b-32ae-a595-4aa4dcd5b713.mp4",
  109. "icon": "http://static.ouj.com/shoes/game/a181082b58cf49aca6a73a02d7196d3e.png",
  110. },
  111. {
  112. "name": "右下踩",
  113. "motion": [11],
  114. "video": "http://static.ouj.com/video/617a5dfc-f3cc-3d06-896d-4907f4b1123b.mp4",
  115. "icon": "http://static.ouj.com/shoes/game/5d210d906bef4fc68b51dbb648449229.png",
  116. },
  117. ],
  118. 10: [
  119. {
  120. "name": "开始",
  121. "motion": [],
  122. "video": "http://static.ouj.com/video/26b2a19e-8c13-31e9-9eeb-186f346114cf.mp4",
  123. },
  124. {
  125. "name": "左右踩",
  126. "motion": [4, 5],
  127. "video": "http://static.ouj.com/video/ad852e3a-37a7-3bf1-8055-009bfd25afea.mp4",
  128. },
  129. {
  130. "name": "跳跃",
  131. "motion": [2],
  132. "video": "http://static.ouj.com/video/02fdb193-18bc-3a59-8871-57d92b935306.mp4",
  133. },
  134. {
  135. "name": "下蹲",
  136. "motion": [3],
  137. "video": "http://static.ouj.com/video/cf2a28dc-56b6-37dd-8523-8b133a43709f.mp4",
  138. },
  139. ],
  140. 9: [
  141. {
  142. "name": "开始",
  143. "motion": [],
  144. "video": "http://static.ouj.com/video/ce0096d5-6d43-3dbd-9b39-51921a3a5ba8.mp4",
  145. },
  146. {
  147. "name": "原地跑",
  148. "motion": [1],
  149. "motion_type": 1,
  150. "video": "http://static.ouj.com/video/fa1ae69f-b753-31dc-b7db-d694370ccdbe.mp4",
  151. },
  152. {
  153. "name": "跳跃",
  154. "motion": [2],
  155. "video": "http://static.ouj.com/video/52e9b690-6a08-3ca4-9b1d-cad322ab0561.mp4",
  156. },
  157. ],
  158. // 2: [
  159. // {
  160. // "name": "原地跑",
  161. // "motion": [1]
  162. // },
  163. // {
  164. // "name": "向左转",
  165. // "motion": [-1]
  166. // },
  167. // {
  168. // "name": "向右转",
  169. // "motion": [-2]
  170. // },
  171. // {
  172. // "name": "跳跃",
  173. // "motion": [14]
  174. // },
  175. // ],
  176. 22: [
  177. {
  178. "name": "开始",
  179. "motion": [],
  180. "video": "http://static.ouj.com/video/05084300-0ddb-384a-9d7d-d5b3679f1114.mp4",
  181. },
  182. {
  183. "name": "石头",
  184. "motion": [15],
  185. "video": "http://static.ouj.com/video/05a7b15c-d94b-340d-a503-2fe39f03381d.mp4",
  186. },
  187. {
  188. "name": "剪刀",
  189. "motion": [16],
  190. "video": "http://static.ouj.com/video/aa864f5d-cd35-3727-a6eb-6b63012fdcc1.mp4",
  191. },
  192. {
  193. "name": "布",
  194. "motion": [17],
  195. "video": "http://static.ouj.com/video/7f5fc141-bc96-3ba1-a453-b8686d3565cf.mp4",
  196. },
  197. ],
  198. 33: [
  199. {
  200. "name": "开始",
  201. "motion": [],
  202. "video": "http://static.ouj.com/video/3155330b-1bc7-3cfe-af21-8efbfc1da7c9.mp4",
  203. },
  204. {
  205. "name": "高抬腿",
  206. "motion": [19, 21],
  207. "video": "http://static.ouj.com/video/ca4819b4-e66b-3a62-84a1-28e3b9a8cc37.mp4",
  208. "icon": "http://static.ouj.com/shoes/game/02d3a2fb8f0045f9ae608a163a30a83f.png",
  209. },
  210. {
  211. "name": "前踢脚",
  212. "motion": [22, 23],
  213. "video": "http://static.ouj.com/video/73c99824-0ee2-3d70-8695-fc4e8eaf9f5d.mp4",
  214. "icon": "http://static.ouj.com/shoes/game/03a39e4a51334fde8cd7ccfa14ad3ac3.png",
  215. },
  216. {
  217. "name": "侧踢脚",
  218. "motion": [18, 20],
  219. "video": "http://static.ouj.com/video/be2841b0-7a08-3e18-8fd6-d4b0d623985f.mp4",
  220. "icon": "http://static.ouj.com/shoes/game/6cdb79103367417ba7fad82585900327.png",
  221. },
  222. {
  223. "name": "下蹲",
  224. "motion": [3],
  225. "video": "http://static.ouj.com/video/85a16233-e70b-37ee-a584-10be044bba8c.mp4",
  226. "icon": "http://static.ouj.com/shoes/game/20d94a43b2cd4404b23bdda556d75ea2.png",
  227. },
  228. ],
  229. };
  230. class GameGuide extends StatefulWidget {
  231. final GameInfoData game;
  232. final bool base;
  233. final bool launch4GameCenter;
  234. final bool detailGuide;
  235. const GameGuide({Key? key, required this.game, this.base = false, this.launch4GameCenter = false, this.detailGuide = false}) : super(key: key);
  236. @override
  237. State<StatefulWidget> createState() {
  238. return _GameGuideState();
  239. }
  240. }
  241. class _GameGuideState extends State<GameGuide> with SubscriptionState {
  242. late Bluetooth _bluetooth;
  243. late SDKApi _sdk;
  244. VideoPlayerController? _videoPlayerController;
  245. int _videoIndex = 0;
  246. bool _topBar = true;
  247. int _topBarTime = 0;
  248. bool _videoReady = false;
  249. bool _videoFirst = false;
  250. bool _motionFinished = false;
  251. bool _motionFinishedUi = false;
  252. int _finishTimes = 0;
  253. int _videoTime = 0;
  254. int _motionStep = 0;
  255. int _motionTime = 0;
  256. Set<int> motionList = Set();
  257. late List<Map<String, dynamic>> actions;
  258. late int gameId;
  259. GlobalKey dialogKey = GlobalKey();
  260. List<GlobalKey<LikeButtonState>> _motionKey = [GlobalKey(), GlobalKey(), GlobalKey()];
  261. @override
  262. void initState() {
  263. super.initState();
  264. gameId = widget.base ? 0 : widget.game.id;
  265. actions = List<Map<String, dynamic>>.from(guideActionMap[gameId] ?? []).map((e) {
  266. e["a"] = false;
  267. e["test"] = 0;
  268. e["finish"] = false;
  269. if (e["motion_type"] == 1) {
  270. e["motion_progress"] = 0;
  271. }
  272. return e;
  273. }).toList();
  274. _bluetooth = GetIt.I<Bluetooth>();
  275. _sdk = _bluetooth.gameSDK();
  276. _bluetooth.gameInit(widget.game.gameType ?? 0);
  277. _bluetooth.setupGameMode4h5(true);
  278. addSubscription(_bluetooth.sdkCmdStream.listen((event) {
  279. int cmd = event;
  280. if (widget.base != true) {
  281. State<StatefulWidget>? dialogState = dialogKey.currentState;
  282. if (dialogState is CustomGameAlertDialogState) {
  283. dialogState.postCmd(cmd);
  284. } else {
  285. if (cmd == 6) {
  286. _back();
  287. }
  288. if (cmd == 5) {
  289. _ok();
  290. }
  291. }
  292. if (cmd == 6) {
  293. _bluetooth.vibrate(200, leftOrRight: 1);
  294. }
  295. if (cmd == 5) {
  296. _bluetooth.vibrate(200, leftOrRight: 2);
  297. }
  298. } else {
  299. if (_videoIndex == -1) return;
  300. if (_addMotion(cmd)) {
  301. setState(() {});
  302. }
  303. }
  304. }));
  305. addSubscription(_bluetooth.dataStream.listen((event) {
  306. if (_videoIndex == -1) return;
  307. bool mm = false;
  308. if (widget.base != true) {
  309. List<int> result = _sdk.getMotion();
  310. mm = _addMotion(1);
  311. int attX = _sdk.getAttX();
  312. double angle = attX / 10000.0 * 180 / (pi);
  313. if (mm != true && angle > 30) {
  314. mm = _addMotion(-1);
  315. }
  316. if (mm != true && angle < -30) {
  317. mm = _addMotion(-1);
  318. }
  319. if (mm != true) {
  320. if (result.any((element) => element > 0)) {
  321. for (var i = 0; i < result.length; i++) {
  322. if (result[i] > 0) {
  323. mm = _addMotion(result[i]);
  324. }
  325. }
  326. }
  327. }
  328. }
  329. if (mm == true) {
  330. setState(() {
  331. // _motion();
  332. });
  333. }
  334. }));
  335. var start = DateTime.now();
  336. _initVideo(0).then((value) async {
  337. int end = 3000 - DateTime.now().difference(start).inMilliseconds;
  338. if (end > 0) await Future.delayed(Duration(milliseconds: end));
  339. if (!mounted) return;
  340. setState(() {
  341. _videoReady = value != null;
  342. _videoPlayerController = value;
  343. _videoPlayerController?.play();
  344. });
  345. });
  346. Wakelock.enable();
  347. WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
  348. SystemChrome.setPreferredOrientations(Platform.isIOS ? [DeviceOrientation.landscapeRight] : [DeviceOrientation.landscapeLeft]);
  349. SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
  350. });
  351. if (isDebugShoe) {
  352. _bluetooth.createGameLog("${widget.game.name}_video");
  353. }
  354. }
  355. _videoState() {
  356. _videoTime = _videoPlayerController?.value.position.inSeconds ?? 0;
  357. if (_videoTime - _topBarTime > 5) {
  358. if (_topBar == true) {
  359. setState(() {
  360. _topBar = false;
  361. });
  362. }
  363. }
  364. }
  365. Future<VideoPlayerController?> _initVideo(int index) async {
  366. try {
  367. var item = actions[index];
  368. var controller = VideoPlayerController.network(item["video"]);
  369. await controller.initialize();
  370. if ((item["motion"] as List).isEmpty) {
  371. controller.addListener(() {
  372. if (!_videoFirst && !controller.value.isPlaying && controller.value.position == controller.value.duration) {
  373. _videoFirst = true;
  374. _jumpVideo(_videoIndex + 1);
  375. }
  376. });
  377. } else {
  378. controller.setLooping(true);
  379. }
  380. return controller;
  381. } catch (e) {
  382. print(e);
  383. showDialog(
  384. context: context,
  385. builder: (context) => CustomAlertDialog(title: '加载视频失败', cancelable: false, textOk: "知道了", ok: () => Navigator.of(context).pop(true)),
  386. ).then((value) {
  387. if (value == true) {
  388. Navigator.of(context).pop([true, true]);
  389. }
  390. });
  391. }
  392. return null;
  393. }
  394. @override
  395. void dispose() {
  396. _bluetooth.uploadGameLog();
  397. _videoPlayerController?.dispose();
  398. super.dispose();
  399. }
  400. _jumpVideo(int index, {bool tap = false}) {
  401. if (_videoPlayerController != null) {
  402. var controller = _videoPlayerController!;
  403. controller.pause();
  404. controller.removeListener(_videoState);
  405. controller.dispose();
  406. }
  407. _videoPlayerController = null;
  408. if (_videoIndex == index) return;
  409. if (index < actions.length) {
  410. _initVideo(index).then((value) {
  411. if (value != null) {
  412. if (mounted) {
  413. setState(() {
  414. if (mounted) {
  415. _videoIndex = index;
  416. actions[index]["a"] = true;
  417. var controller = value;
  418. controller.addListener(_videoState);
  419. if (mounted)
  420. controller.play();
  421. _videoPlayerController = controller;
  422. } else {
  423. value.dispose();
  424. }
  425. });
  426. } else {
  427. value.dispose();
  428. }
  429. }
  430. });
  431. } else {
  432. Future.delayed(Duration(seconds: 1)).then((value) {
  433. if (mounted) {
  434. setState(() {
  435. _topBar = true;
  436. _motionFinishedUi = true;
  437. });
  438. _videoPlayerController?.pause();
  439. Future.delayed(Duration(seconds: 2)).then((value) async {
  440. if (mounted) {
  441. if (widget.launch4GameCenter != true) {
  442. await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  443. await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top]);
  444. }
  445. Navigator.of(context).pop([true, true]);
  446. }
  447. });
  448. }
  449. });
  450. }
  451. }
  452. bool _addMotion(int m) {
  453. bool result = false;
  454. bool vibrate = false;
  455. // for (var i = 0; i < actions.length; i++) {
  456. var e = actions[_videoIndex];
  457. // print("========== $m ${e["a"]} ${e["motion"]} ${(e["motion"] as List).contains(m)} ${motionList.contains(m)}");
  458. if (e["a"] == true && (e["motion"] as List).contains(m) && !motionList.contains(m)) {
  459. bool finish = false;
  460. if (e["motion_progress"] != null) {
  461. int progress = e["motion_progress"];
  462. int stepCount = _sdk.getStepCount();
  463. print("11111111111111111111111111111 ${_sdk.getStepFreq()} $stepCount");
  464. if (_sdk.getStepFreq() > 80) {
  465. if (stepCount > _motionStep) {
  466. _motionStep = stepCount;
  467. progress += 3;
  468. }
  469. } else {
  470. progress -= 1;
  471. }
  472. progress = max(0, progress);
  473. e["motion_progress"] = progress;
  474. finish = progress >= 100;
  475. if (finish) {
  476. e["test"] = 3;
  477. _motionKey[0].currentState?.onTap();
  478. }
  479. } else {
  480. var now = DateTime.now().millisecondsSinceEpoch;
  481. if (now - _motionTime > 800) {
  482. _motionTime = now;
  483. int test = (e["test"] ?? 0) + 1;
  484. e["test"] = test;
  485. finish = test >= 3;
  486. _motionKey[min(test - 1, 2)].currentState?.onTap();
  487. vibrate = true;
  488. }
  489. }
  490. result = true;
  491. if (finish && e["finish"] != true) {
  492. e["finish"] = true;
  493. motionList.add(m);
  494. _videoPlayerController?.pause();
  495. Future.delayed(Duration(seconds: 1)).then((value) => _jumpVideo(_videoIndex + 1));
  496. }
  497. // break;
  498. }
  499. // }
  500. if (vibrate == true) {
  501. _bluetooth.vibrate(200);
  502. }
  503. return result;
  504. }
  505. void _handleVisibilityChanged(VisibilityInfo info) {
  506. if (!mounted) return;
  507. if (info.visibleFraction == 0) {
  508. if (_videoPlayerController?.value.isInitialized == true) _videoPlayerController?.pause();
  509. } else {
  510. // _videoPlayerController.play();
  511. }
  512. }
  513. _back() {
  514. showDialog(
  515. context: context,
  516. builder: (context) => CustomGameAlertDialog(title: '是否退出教程', key: dialogKey, ok: () => Navigator.of(context).pop(true)),
  517. useSafeArea: false,
  518. ).then((value) {
  519. if (value == true) {
  520. Navigator.of(context).pop();
  521. if (widget.launch4GameCenter != true) {
  522. SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  523. SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top]);
  524. }
  525. }
  526. });
  527. }
  528. _ok() {
  529. ValueNotifier<bool> selected = ValueNotifier<bool>(false);
  530. showDialog(
  531. context: context,
  532. useSafeArea: false,
  533. builder: (context) => CustomGameAlertDialog(
  534. title: '是否跳过教程',
  535. key: dialogKey,
  536. child: Center(
  537. child: InkWell(
  538. onTap: () {
  539. selected.value = !selected.value;
  540. },
  541. child: Row(
  542. mainAxisSize: MainAxisSize.min,
  543. children: [
  544. ValueListenableBuilder<bool>(
  545. valueListenable: selected,
  546. builder: (context, value, _) {
  547. return Padding(
  548. padding: const EdgeInsets.all(8.0),
  549. child: Image.asset("lib/assets/img/${value ? "pop_icon_conneted" : "pop_icon_choose_normal"}.png"),
  550. );
  551. }),
  552. Text(
  553. "不再显示教程",
  554. style: Theme.of(context).textTheme.bodyText2,
  555. ),
  556. ],
  557. ),
  558. ),
  559. ),
  560. ok: () => Navigator.of(context).pop(true)),
  561. ).then((value) {
  562. if (value == true) {
  563. Navigator.of(context).pop([value, selected.value]);
  564. }
  565. });
  566. }
  567. @override
  568. Widget build(BuildContext context) {
  569. return WillPopScope(
  570. onWillPop: () async {
  571. if (_videoReady != true) {
  572. if (widget.launch4GameCenter != true) {
  573. SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  574. SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top]);
  575. }
  576. return true;
  577. }
  578. return false;
  579. },
  580. child: Scaffold(
  581. backgroundColor: Colors.white,
  582. body: GestureDetector(
  583. behavior: HitTestBehavior.opaque,
  584. onTap: () {
  585. setState(() {
  586. _topBar = !_topBar;
  587. _topBarTime = _videoTime;
  588. });
  589. },
  590. child: Stack(
  591. fit: StackFit.expand,
  592. children: [
  593. if (_videoPlayerController != null)
  594. Center(
  595. child: AspectRatio(
  596. aspectRatio: _videoPlayerController!.value.aspectRatio,
  597. child: VisibilityDetector(
  598. key: ValueKey(widget.game.id),
  599. onVisibilityChanged: _handleVisibilityChanged,
  600. child: LayoutBuilder(builder: (context, constraints) {
  601. return Container(
  602. child: Stack(
  603. fit: StackFit.expand,
  604. children: [
  605. VideoPlayer(_videoPlayerController!),
  606. if (_videoIndex > -1 && (actions[_videoIndex]["motion"] as List).isNotEmpty == true)
  607. Positioned(
  608. top: constraints.maxHeight * 0.75,
  609. left: constraints.maxWidth * .56,
  610. right: constraints.maxWidth * .08,
  611. child: Container(
  612. child: Column(
  613. children: [
  614. actions[_videoIndex]["motion_progress"] != null
  615. ? Text(
  616. "跟着视频跑起来",
  617. style: Theme.of(context).textTheme.bodyText2?.copyWith(fontSize: 16.0, color: Theme.of(context).colorScheme.secondary),
  618. )
  619. : Text(
  620. "跟着视频做三次",
  621. style: Theme.of(context).textTheme.bodyText2?.copyWith(fontSize: 16.0, color: Theme.of(context).colorScheme.secondary),
  622. ),
  623. actions[_videoIndex]["motion_progress"] != null
  624. ? Container(
  625. width: 160,
  626. height: 40,
  627. child: Row(
  628. mainAxisSize: MainAxisSize.min,
  629. children: [
  630. Expanded(
  631. child: Container(
  632. child: LinearProgressIndicator(
  633. valueColor: AlwaysStoppedAnimation(
  634. Theme.of(context).colorScheme.secondary,
  635. ),
  636. backgroundColor: Theme.of(context).scaffoldBackgroundColor,
  637. value: (actions[_videoIndex]["motion_progress"] as int) / 100.0,
  638. minHeight: 5,
  639. ),
  640. ),
  641. ),
  642. LikeButton(
  643. likeBuilder: (bool isLiked) {
  644. if (actions[_videoIndex]["icon"] != null) {
  645. return !isLiked
  646. ? ColorFiltered(
  647. colorFilter: ColorFilter.matrix(<double>[
  648. 0.2126,
  649. 0.7152,
  650. 0.0722,
  651. 0,
  652. 0,
  653. 0.2126,
  654. 0.7152,
  655. 0.0722,
  656. 0,
  657. 0,
  658. 0.2126,
  659. 0.7152,
  660. 0.0722,
  661. 0,
  662. 0,
  663. 0,
  664. 0,
  665. 0,
  666. 1,
  667. 0,
  668. ]),
  669. child: CachedNetworkImage(imageUrl: '${actions[_videoIndex]["icon"]}'),
  670. )
  671. : CachedNetworkImage(
  672. imageUrl: '${actions[_videoIndex]["icon"]}',
  673. );
  674. }
  675. return isLiked
  676. ? Image.asset("lib/assets/img/pop_icon_conneted.png")
  677. : Image.asset(
  678. "lib/assets/img/pop_icon_choose_normal.png",
  679. color: const Color(0xff666666),
  680. );
  681. },
  682. isLiked: (actions[_videoIndex]["test"] ?? 0) >= 3,
  683. key: _motionKey[0],
  684. ),
  685. ],
  686. ),
  687. margin: const EdgeInsets.fromLTRB(32, 8, 0, 8),
  688. )
  689. : Row(
  690. children: [
  691. for (var i = 0; i < 3; i++)
  692. Padding(
  693. padding: const EdgeInsets.all(8.0),
  694. child: LikeButton(
  695. likeBuilder: (bool isLiked) {
  696. if (actions[_videoIndex]["icon"] != null) {
  697. return !isLiked
  698. ? ColorFiltered(
  699. colorFilter: ColorFilter.matrix(<double>[
  700. 0.2126,
  701. 0.7152,
  702. 0.0722,
  703. 0,
  704. 0,
  705. 0.2126,
  706. 0.7152,
  707. 0.0722,
  708. 0,
  709. 0,
  710. 0.2126,
  711. 0.7152,
  712. 0.0722,
  713. 0,
  714. 0,
  715. 0,
  716. 0,
  717. 0,
  718. 1,
  719. 0,
  720. ]),
  721. child: CachedNetworkImage(imageUrl: '${actions[_videoIndex]["icon"]}'),
  722. )
  723. : CachedNetworkImage(
  724. imageUrl: '${actions[_videoIndex]["icon"]}',
  725. );
  726. }
  727. return isLiked
  728. ? Image.asset("lib/assets/img/pop_icon_conneted.png")
  729. : Image.asset(
  730. "lib/assets/img/pop_icon_choose_normal.png",
  731. color: const Color(0xff666666),
  732. );
  733. },
  734. isLiked: (actions[_videoIndex]["test"] ?? 0) > i,
  735. key: _motionKey[i],
  736. ),
  737. ),
  738. ],
  739. mainAxisSize: MainAxisSize.min,
  740. ),
  741. ],
  742. ),
  743. ),
  744. )
  745. ],
  746. ),
  747. );
  748. })),
  749. ),
  750. ),
  751. Align(
  752. alignment: Alignment.centerLeft,
  753. child: OpacityAnimatedWidget.tween(
  754. opacityEnabled: 1,
  755. opacityDisabled: 0,
  756. duration: Duration(seconds: 3),
  757. enabled: _videoReady,
  758. child: Container(
  759. constraints: BoxConstraints(maxWidth: 148 + MediaQuery.of(context).viewPadding.left),
  760. child: Column(
  761. mainAxisSize: MainAxisSize.min,
  762. crossAxisAlignment: CrossAxisAlignment.start,
  763. children: [
  764. Column(
  765. children: actions.where((element) => (element['motion'] as List).isNotEmpty).map((e) {
  766. // bool done = motionList.any((element) => (e['motion'] as List<int>).contains(element));
  767. // bool enable = e["a"] == true;
  768. int index = actions.indexOf(e);
  769. var child = GestureDetector(
  770. onTap: () {
  771. _jumpVideo(index, tap: true);
  772. },
  773. behavior: HitTestBehavior.opaque,
  774. child: Container(
  775. margin: const EdgeInsets.symmetric(vertical: 4.0),
  776. padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 30),
  777. decoration: index == _videoIndex ? BoxDecoration(color: Theme.of(context).colorScheme.secondary) : null,
  778. alignment: Alignment.centerLeft,
  779. child: Padding(
  780. padding: EdgeInsets.only(left: MediaQuery.of(context).viewPadding.left),
  781. child: Row(
  782. mainAxisSize: MainAxisSize.min,
  783. children: [
  784. // Padding(
  785. // padding: const EdgeInsets.only(right: 6.0),
  786. // child: done
  787. // ? Image.asset("lib/assets/img/pop_icon_conneted.png")
  788. // : enable
  789. // ? Image.asset(
  790. // "lib/assets/img/pop_icon_choose_normal.png",
  791. // color: const Color(0xff666666),
  792. // )
  793. // : Image.asset(
  794. // "lib/assets/img/pop_icon_choose_normal.png",
  795. // color: const Color(0xffcecece),
  796. // ),
  797. // ),
  798. ConstrainedBox(
  799. constraints: BoxConstraints(minWidth: 40),
  800. child: Text(
  801. "${e['name']}",
  802. style: index == _videoIndex ? Theme.of(context).textTheme.bodyText2?.copyWith(color: Colors.white, fontSize: 22) : Theme.of(context).textTheme.bodyText2?.copyWith(fontSize: 16),
  803. ),
  804. ),
  805. Container(
  806. padding: const EdgeInsets.only(left: 10),
  807. child: e['test'] >= 3
  808. ? Image.asset(
  809. "lib/assets/img/topbar_ok.png",
  810. color: const Color(0xff666666),
  811. width: 10,
  812. fit: BoxFit.cover,
  813. )
  814. : null,
  815. ),
  816. ],
  817. ),
  818. ),
  819. ));
  820. return index == _videoIndex
  821. ? ClipPath(
  822. clipper: _TabPath(),
  823. child: child,
  824. )
  825. : child;
  826. }).toList(),
  827. ),
  828. ],
  829. ),
  830. ),
  831. ),
  832. ),
  833. // Positioned(
  834. // bottom: 8,
  835. // left: 16,
  836. // right: 16,
  837. // child: ValueListenableBuilder<VideoPlayerValue>(
  838. // valueListenable: _videoPlayerController,
  839. // builder: (context, v, __) {
  840. // return Row(
  841. // crossAxisAlignment: CrossAxisAlignment.center,
  842. // children: [
  843. // Padding(
  844. // padding: EdgeInsets.symmetric(horizontal: 5.0),
  845. // child: Text(DateFormat.toVideoTime(v.position.inSeconds), style: Theme.of(context).textTheme.bodyText1?.copyWith(fontSize: 10)),
  846. // ),
  847. // Expanded(
  848. // child: SizedBox(
  849. // height: 2,
  850. // width: double.infinity,
  851. // child: VideoProgressIndicator(
  852. // _videoPlayerController,
  853. // allowScrubbing: false,
  854. // colors: VideoProgressColors(backgroundColor: const Color(0xffdcdcdc), bufferedColor: Colors.transparent, playedColor: Theme.of(context).colorScheme.secondary),
  855. // padding: EdgeInsets.zero,
  856. // ),
  857. // ),
  858. // ),
  859. // Padding(
  860. // padding: EdgeInsets.symmetric(horizontal: 5.0),
  861. // child: Text(DateFormat.toVideoTime(v.duration.inSeconds), style: Theme.of(context).textTheme.bodyText1?.copyWith(fontSize: 10)),
  862. // ),
  863. // ],
  864. // );
  865. // }),
  866. // ),
  867. if (_videoReady != true)
  868. Container(
  869. color: Colors.white,
  870. child: Center(
  871. child: Column(
  872. mainAxisSize: MainAxisSize.min,
  873. children: [
  874. Text("欢迎使用趣动智能鞋",
  875. style: Theme.of(context).textTheme.headline1?.copyWith(
  876. fontSize: 30,
  877. )),
  878. SizedBox(
  879. height: 20,
  880. ),
  881. RichText(
  882. text: TextSpan(
  883. style: Theme.of(context).textTheme.bodyText2?.copyWith(fontSize: 18.0),
  884. children: <InlineSpan>[
  885. TextSpan(text: '下面开始'),
  886. TextSpan(
  887. text: '新手教程',
  888. style: Theme.of(context).textTheme.bodyText2?.copyWith(fontSize: 18, color: Theme.of(context).colorScheme.secondary),
  889. ),
  890. ],
  891. ),
  892. ),
  893. ],
  894. ),
  895. ),
  896. ),
  897. if (_motionFinishedUi == true)
  898. Container(
  899. color: Colors.white,
  900. child: Center(
  901. child: RichText(
  902. text: TextSpan(
  903. style: Theme.of(context).textTheme.bodyText2?.copyWith(fontSize: 18.0),
  904. children: <InlineSpan>[
  905. TextSpan(text: '您已完成 '),
  906. TextSpan(
  907. text: '${widget.base ? "基础动作" : widget.game.name}',
  908. style: Theme.of(context).textTheme.bodyText2?.copyWith(fontSize: 18, color: Theme.of(context).colorScheme.secondary),
  909. ),
  910. TextSpan(text: ' 新手教程,祝您运动愉快'),
  911. ],
  912. ),
  913. ),
  914. ),
  915. ),
  916. Positioned(
  917. left: 0,
  918. right: 0,
  919. top: 0,
  920. child: Offstage(
  921. offstage: !_topBar,
  922. child: SafeArea(
  923. child: Padding(
  924. padding: const EdgeInsets.all(16.0),
  925. child: Row(
  926. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  927. children: [
  928. InkWell(
  929. onTap: _back,
  930. child: Row(
  931. children: [
  932. arrowBack(),
  933. Text(
  934. widget.base ? "返回" : "左踮脚 · 返回",
  935. style: Theme.of(context).textTheme.subtitle1,
  936. )
  937. ],
  938. ),
  939. ),
  940. // Text(
  941. // "${widget.base ? "基础动作" : widget.game.name}操作指引",
  942. // style: Theme.of(context).textTheme.headline1,
  943. // ),
  944. if (widget.detailGuide != true)
  945. InkWell(
  946. onTap: _ok,
  947. child: Row(
  948. children: [
  949. Text(
  950. widget.base ? "跳过" : "右踮脚 · 跳过",
  951. style: Theme.of(context).textTheme.subtitle1,
  952. ),
  953. RotatedBox(
  954. quarterTurns: 90,
  955. child: arrowBack(),
  956. ),
  957. ],
  958. ),
  959. ),
  960. ],
  961. ),
  962. ),
  963. ),
  964. ),
  965. ),
  966. ],
  967. ),
  968. ),
  969. ),
  970. );
  971. }
  972. }
  973. class _TabPath extends CustomClipper<Path> {
  974. @override
  975. Path getClip(Size size) {
  976. var path = Path();
  977. path.moveTo(0, 0);
  978. path.lineTo(size.width - 20, 0);
  979. path.lineTo(size.width, size.height / 2);
  980. path.lineTo(size.width - 20, size.height);
  981. path.lineTo(0, size.height);
  982. path.close();
  983. return path;
  984. }
  985. @override
  986. bool shouldReclip(CustomClipper<Path> oldClipper) {
  987. return false;
  988. }
  989. }