import 'dart:async'; import 'dart:io'; import 'package:animated_text_kit/animated_text_kit.dart'; import 'package:audio_session/audio_session.dart'; import 'package:dotted_line/dotted_line.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get_it/get_it.dart'; import 'package:just_audio/just_audio.dart'; import 'package:sport/provider/bluetooth.dart'; import 'package:sport/services/app_subscription_state.dart'; import 'package:sport/utils/DateFormat.dart'; import 'package:sport/widgets/button_cancel.dart'; import 'package:sport/widgets/dialog/game_alert_dialog.dart'; import 'package:sport/widgets/image.dart'; import 'package:video_player/video_player.dart'; /** * @TODO 一、服务器接口 * 1、数据详情接口 * 2、运动提交接口、保存的内容、所需要回显的位置及具体内容需提前规则 * 3、排行榜(修改)升序 * * 二、算法 * 1、目前只使用了在三轮车游戏中使用的开合跳,来测试demo * 2、具体需要支持哪些动作?识别动作算法需重新编辑 * * 三、运动的配置 * 1、动作的次数,难度选择等 * 2、运动过程中是否要添加其它提示语音、引导语音等 */ class SportDetail extends StatefulWidget { final int type; const SportDetail({Key? key, required this.type}) : super(key: key); @override State createState() => _State(); } class _State extends State with SubscriptionState { late VideoPlayerController _videoPlayerController; late AudioPlayer _audioPlayer; Timer? _timer; final List groupLabel = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十"]; List missions = List.generate(5, (i) => Mission()); int _index = 0; int _missionIndex = 0; int _restIndex = -1; Duration _duration = Duration.zero; late Bluetooth _bluetooth; final GlobalKey dialogKey = GlobalKey(); bool _startui = false; var _buttonIndex = 0; @override void initState() { super.initState(); _videoPlayerController = VideoPlayerController.network("https://static.ouj.com/hiyd_cms/file/4bb98b9c7cb0441cbea0a96ff73bae93.mp4"); _videoPlayerController.setLooping(true); _videoPlayerController.initialize().then((value) { setState(() { // print("111111111111111111111111111 ${_videoPlayerController.value.duration}"); _duration = _videoPlayerController.value.duration; setState(() { _startui = true; }); // _startMission(); }); }); _videoPlayerController.addListener(() { // print("11111111111111111111 ${_videoPlayerController.value.isPlaying} ${_videoPlayerController.value}"); // if(_videoPlayerController.value.position == _videoPlayerController.value.duration){ // _videoPlayerController.seekTo(Duration.zero); // _videoPlayerController.play(); // } }); _audioPlayer = AudioPlayer(handleInterruptions: false); AudioSession.instance.then((audioSession) async { await audioSession.configure(AudioSessionConfiguration.music().copyWith(avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.mixWithOthers | AVAudioSessionCategoryOptions.duckOthers, avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.notifyOthersOnDeactivation, androidAudioFocusGainType: AndroidAudioFocusGainType.gainTransientMayDuck)); addSubscription(_audioPlayer.playerStateStream.listen((event) { if (event.processingState == ProcessingState.completed) {} if (Platform.isAndroid) { audioSession.setActive(event.processingState == ProcessingState.ready); } })); }); WidgetsBinding.instance?.addPostFrameCallback((timeStamp) { SystemChrome.setPreferredOrientations(Platform.isIOS ? [DeviceOrientation.landscapeRight] : [DeviceOrientation.landscapeLeft]); SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); }); _bluetooth = GetIt.I(); _bluetooth.gameInit(6); _bluetooth.setupGameMode4h5(true); addSubscription(_bluetooth.sdkMotionStream.listen((event) { List result = event; for (var i in result) { if (i == 14) { setState(() { var mission = missions[_missionIndex]; _index = mission.count += 1; _playAudio(["number/${_index}"]); if (_index == mission.target) { if (_missionIndex < missions.length - 1) { _startRest(); } else { _finishMission(); } } }); break; } } })); } @override void dispose() { SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top]); _timer?.cancel(); _videoPlayerController.dispose(); _audioPlayer.dispose(); _bluetooth.setupGameMode4h5(false); super.dispose(); } _startMission() { if (_missionIndex >= missions.length) { Navigator.pop(context); return; } _index = 0; setState(() { _restIndex = -1; _startui = false; }); _timer?.cancel(); _timer = Timer.periodic(const Duration(seconds: 1), (timer) { setState(() { missions[_missionIndex].time += 1; // var mission = missions[_missionIndex]; // _index = mission.count += 1; // if (_index == mission.target) { // timer.cancel(); // if (_missionIndex < missions.length - 1) { // _startRest(); // } else { // _finishMission(); // } // } }); }); _videoPlayerController.play(); } _startRest() { _timer?.cancel(); setState(() { _restIndex = 0; }); _videoPlayerController.pause(); _videoPlayerController.seekTo(Duration.zero); _timer = Timer.periodic(Duration(seconds: 1), (timer) { ++_restIndex; if (_restIndex > 10) { timer.cancel(); setState(() { _missionIndex++; _startMission(); }); } else { setState(() {}); } }); } _finishMission() { showDialog( context: context, builder: (context) => CustomGameAlertDialog( title: '是否退出教程', key: dialogKey, ok: () => Navigator.of(context).pop(true), custom: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(11.0)), padding: const EdgeInsets.all(20.0), child: Row( mainAxisSize: MainAxisSize.min, children: [ Column( children: [ Text( "恭喜你已完成训练", style: Theme.of(context).textTheme.subtitle1?.copyWith(color: Theme.of(context).colorScheme.secondary, fontSize: 20.0), ), Text( "完成动作", style: Theme.of(context).textTheme.subtitle1, ), Text( "运动消耗", style: Theme.of(context).textTheme.subtitle1, ), Text( "运动时长", style: Theme.of(context).textTheme.subtitle1, ) ], ), SizedBox(height: 140, child: DottedLine( direction: Axis.vertical,lineLength: double.infinity, dashColor: Theme.of(context).dividerColor,),), Column(children: [ Text( "排行榜", style: Theme.of(context).textTheme.subtitle1, ) ],), ], ), ), const SizedBox( height: 24, ), Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ _buttonIndex == 0 ? CancelButton( backgroundColor: Theme.of(context).colorScheme.secondary, height: 35, width: 135, textColor: Colors.white, callback: () { Navigator.of(context).pop(false); }, content: "再来一次") : CancelButton( height: 35, width: 135, callback: () { Navigator.of(context).pop(false); }, content: "再来一次"), const SizedBox( width: 16, ), _buttonIndex == 1 ? CancelButton(backgroundColor: Theme.of(context).colorScheme.secondary, height: 35, width: 135, textColor: Colors.white, callback: () => Navigator.of(context).pop(true), content: "结束运动") : CancelButton(height: 35, width: 135, callback: () => Navigator.of(context).pop(true), content: "结束运动") ], ), ), ], ), ), ), useSafeArea: false, ).then((value) { if (value == true) { Navigator.of(context).pop(); }else{ setState(() { _index = 0; _missionIndex = 0; _restIndex = -1; missions.clear(); missions = List.generate(5, (i) => Mission()); _startMission(); }); } }); } _playAudio(List audios) { _audioPlayer.setAudioSource(ConcatenatingAudioSource(children: audios.map((e) => AudioSource.uri(Uri.parse("asset:///assets/audio/$e.mp3"))).toList())).then((value) => _audioPlayer.play()); } @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { return false; }, child: Scaffold( backgroundColor: Colors.black, body: Center( child: AspectRatio( aspectRatio: _videoPlayerController.value.aspectRatio, child: Stack( fit: StackFit.expand, children: [ VideoPlayer(_videoPlayerController), Align( alignment: Alignment.bottomCenter, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ for (var i = 0; i < missions.length; i++) Expanded( child: Container( padding: i > 0 ? const EdgeInsets.only(left: 6) : EdgeInsets.zero, child: Column( children: [ Padding( padding: const EdgeInsets.all(6.0), child: Text( "第${groupLabel[i]}组", style: Theme.of(context).textTheme.subtitle1?.copyWith(color: Colors.white), ), ), LinearProgressIndicator( value: _missionIndex > i ? 1.0 : _missionIndex == i && missions[i].count > 0 ? missions[i].count / missions[i].target : 0.0, backgroundColor: Colors.grey, ), ], mainAxisSize: MainAxisSize.min, ), ), ) ], ), ), Positioned( top: 20, right: 0, child: Container( decoration: BoxDecoration(borderRadius: BorderRadius.only(topLeft: Radius.circular(50.0), bottomLeft: Radius.circular(50.0)), color: Colors.black.withOpacity(.5)), child: InkWell( onTap: () { Navigator.pop(context); }, child: Padding( padding: const EdgeInsets.fromLTRB(12.0, 10.0, 12.0, 10.0), child: Row( children: [ arrowBackShoe(), Text( "左踮脚 · 返回", style: Theme.of(context).textTheme.subtitle1?.copyWith(color: Colors.white), ) ], ), ), ), )), Positioned( top: 30, child: SafeArea( child: Container( constraints: BoxConstraints(maxWidth: 185), padding: const EdgeInsets.all(27.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( decoration: BoxDecoration(borderRadius: BorderRadius.circular(8.0), color: Colors.black.withOpacity(.5)), padding: const EdgeInsets.all(16.0), child: Center( child: Text( "开合跳", style: Theme.of(context).textTheme.headline4?.copyWith(fontSize: 18.0), ), ), ), Container( width: double.infinity, decoration: BoxDecoration(borderRadius: BorderRadius.circular(8.0), color: Colors.black.withOpacity(.5)), margin: const EdgeInsets.only(top: 13.0), padding: const EdgeInsets.all(16.0), child: Column( children: [ Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Text( "第${groupLabel[_missionIndex]}组", style: Theme.of(context).textTheme.subtitle1?.copyWith(color: Colors.white), ), ), Row( mainAxisSize: MainAxisSize.min, children: [ Text( "${missions[_missionIndex].count.toString().padLeft(2, "0")}", style: Theme.of(context).textTheme.headline4?.copyWith(fontSize: 30.0, color: Theme.of(context).colorScheme.secondary), ), Text( " / ", style: Theme.of(context).textTheme.headline4?.copyWith(fontSize: 30.0), ), Text( "${missions[_missionIndex].target}", style: Theme.of(context).textTheme.headline4?.copyWith( fontSize: 30.0, ), ), ], ), Padding( padding: const EdgeInsets.only(top: 8.0), child: Text(DateFormat.toVideoTime(missions[_missionIndex].time), style: Theme.of(context).textTheme.subtitle1?.copyWith(fontSize: 18.0, color: Colors.white)), ) ], )), ], ), ), ), ), if (_startui == true) Container( color: Colors.black54, child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( height: 120, child: DefaultTextStyle( style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 120.0, fontFamily: "DIN", color: Colors.white), child: AnimatedTextKit( totalRepeatCount: 1, pause: Duration.zero, onFinished: () => _startMission(), animatedTexts: [ for (var i = 5; i > 0; i--) ScaleAnimatedText('$i', duration: const Duration(milliseconds: 1000)), ScaleAnimatedText('GO', duration: const Duration(milliseconds: 1000)), ], onTap: () {}, ), ), ), Padding( padding: const EdgeInsets.only(top: 27.0, bottom: 8.0), child: Text("运动即将开始", style: Theme.of(context).textTheme.headline4?.copyWith(fontSize: 18.0)), ), Text("请做好拉伸准备,避免肌肉拉伤", style: Theme.of(context).textTheme.subtitle1?.copyWith(color: Colors.white)), ], ), ), ), if (_restIndex > -1) Container( color: Colors.black54, child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 100.0, height: 100.0, child: Stack( fit: StackFit.expand, children: [ Center( child: Padding( padding: const EdgeInsets.only(top: 10.0), child: Text( "${10 - _restIndex}", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 60.0, fontFamily: "DIN", color: Theme.of(context).colorScheme.secondary), ), ), ), CircularProgressIndicator( value: _restIndex / 10.0, strokeWidth: 6, backgroundColor: Theme.of(context).colorScheme.secondary, valueColor: AlwaysStoppedAnimation( Color(0xff82785c), )) ], ), ), Padding( padding: const EdgeInsets.only(top: 27.0, bottom: 8.0), child: Text("第${groupLabel[_missionIndex + 1]}组运动即将开始", style: Theme.of(context).textTheme.headline4?.copyWith(fontSize: 18.0)), ), Text("请做好充分休息,让肌肉放松下", style: Theme.of(context).textTheme.subtitle1?.copyWith(color: Colors.white)), ], ), ), ), ], ), ), ), ), ); } } class Mission { int state = 0; int count = 0; int target = 10; int time = 0; }