From c623c7b25a82a33deaa820299b3e258bc97f79f8 Mon Sep 17 00:00:00 2001 From: wuqifeng <540416539@qq.com> Date: Fri, 2 Aug 2024 00:38:17 +0800 Subject: [PATCH] feat:语音跟读作答结果动效&语音 --- assets/lotties/excellent.zip | Bin 0 -> 34886 bytes assets/lotties/good.zip | Bin 0 -> 9765 bytes assets/lotties/great.zip | Bin 0 -> 12204 bytes assets/sounds/excellent.mp3 | Bin 0 -> 29026 bytes assets/sounds/good.mp3 | Bin 0 -> 20600 bytes assets/sounds/great.mp3 | Bin 0 -> 25281 bytes assets/sounds/try_again.mp3 | Bin 0 -> 163086 bytes lib/common/utils/show_star_reward_dialog.dart | 27 +++++++++++++++++++++++++++ lib/common/widgets/cheer_reward_widget.dart | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/common/widgets/star_reward_widget.dart | 4 ++-- lib/pages/practice/bloc/topic_picture_bloc.dart | 34 +++++++++++++++++++++++++++++++--- lib/pages/reading/reading_page.dart | 37 +++++++++++++++++-------------------- lib/utils/audio_player_util.dart | 6 +++++- 13 files changed, 205 insertions(+), 26 deletions(-) create mode 100644 assets/lotties/excellent.zip create mode 100644 assets/lotties/good.zip create mode 100644 assets/lotties/great.zip create mode 100644 assets/sounds/excellent.mp3 create mode 100644 assets/sounds/good.mp3 create mode 100644 assets/sounds/great.mp3 create mode 100644 assets/sounds/try_again.mp3 create mode 100644 lib/common/widgets/cheer_reward_widget.dart diff --git a/assets/lotties/excellent.zip b/assets/lotties/excellent.zip new file mode 100644 index 0000000..63b1b2f Binary files /dev/null and b/assets/lotties/excellent.zip differ diff --git a/assets/lotties/good.zip b/assets/lotties/good.zip new file mode 100644 index 0000000..5ecb036 Binary files /dev/null and b/assets/lotties/good.zip differ diff --git a/assets/lotties/great.zip b/assets/lotties/great.zip new file mode 100644 index 0000000..ad12ecd Binary files /dev/null and b/assets/lotties/great.zip differ diff --git a/assets/sounds/excellent.mp3 b/assets/sounds/excellent.mp3 new file mode 100644 index 0000000..c95e98e Binary files /dev/null and b/assets/sounds/excellent.mp3 differ diff --git a/assets/sounds/good.mp3 b/assets/sounds/good.mp3 new file mode 100644 index 0000000..e3c9ff3 Binary files /dev/null and b/assets/sounds/good.mp3 differ diff --git a/assets/sounds/great.mp3 b/assets/sounds/great.mp3 new file mode 100644 index 0000000..5bde58d Binary files /dev/null and b/assets/sounds/great.mp3 differ diff --git a/assets/sounds/try_again.mp3 b/assets/sounds/try_again.mp3 new file mode 100644 index 0000000..7e95479 Binary files /dev/null and b/assets/sounds/try_again.mp3 differ diff --git a/lib/common/utils/show_star_reward_dialog.dart b/lib/common/utils/show_star_reward_dialog.dart index 08cbdeb..aa3155f 100644 --- a/lib/common/utils/show_star_reward_dialog.dart +++ b/lib/common/utils/show_star_reward_dialog.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import '../widgets/cheer_reward_widget.dart'; import '../widgets/star_reward_widget.dart'; void showStarRewardDialog(BuildContext context, { @@ -26,4 +27,30 @@ void showStarRewardDialog(BuildContext context, { ); }, ); +} + +void showCheerRewardDialog(BuildContext context, { + required String lottieFile, + double width = 200, + double height = 200, +}) { + showDialog( + context: context, + barrierDismissible: false, // 点击对话框外部不关闭对话框 + builder: (BuildContext context) { + return Dialog( + backgroundColor: Colors.transparent, // 设置对话框背景为透明 + insetPadding: const EdgeInsets.all(0), // 去除对话框的内边距 + child: CheerRewardWidget( + lottieFile: lottieFile, + width: width, + height: height, + isPlaying: true, + onAnimationEnd: () { + Navigator.of(context).pop(); // 关闭对话框 + }, + ), + ); + }, + ); } \ No newline at end of file diff --git a/lib/common/widgets/cheer_reward_widget.dart b/lib/common/widgets/cheer_reward_widget.dart new file mode 100644 index 0000000..6281397 --- /dev/null +++ b/lib/common/widgets/cheer_reward_widget.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:lottie/lottie.dart'; + +import '../../utils/log_util.dart'; + +class CheerRewardWidget extends StatefulWidget { + final String lottieFile; + final double width; + final double height; + final bool isPlaying; + final VoidCallback onAnimationEnd; + + const CheerRewardWidget({ + Key? key, + required this.lottieFile, + required this.width, + required this.height, + required this.isPlaying, + required this.onAnimationEnd, + }) : super(key: key); + + @override + _CheerRewardWidgetState createState() => _CheerRewardWidgetState(); +} + +class _CheerRewardWidgetState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _controller; + late final Future _futureComposition; + bool _isVisible = false; + static const String TAG = "CheerRewardWidget"; + + @override + void initState() { + super.initState(); + _controller = AnimationController(vsync: this); + _loadComposition(); + } + + @override + void didUpdateWidget(CheerRewardWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.isPlaying && !_controller.isAnimating) { + _startAnimation(); + } + } + + void _loadComposition() { + // final composition = await AssetLottie('assets/lotties/recorder_input.zip').load(); + // setState(() { + // _composition = composition; + // _controller.duration = _composition.duration; + // }); + + _futureComposition = _loadLottieComposition(); + + if (widget.isPlaying) { + _startAnimation(); + } + } + + void _startAnimation() { + Log.d("$TAG _startAnimation"); + setState(() { + _isVisible = true; + }); + + _futureComposition.then((composition) { + Log.d("$TAG _futureComposition.then duration=${composition.duration}"); + _controller.duration = composition.duration; + _controller.forward().whenCompleteOrCancel(() { + setState(() { + _isVisible = false; + }); + widget.onAnimationEnd(); // 调用外部回调函数 + }); + }); + } + + Future _loadLottieComposition() async { + return await AssetLottie(widget.lottieFile).load(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Center( + child: SizedBox( + width: widget.width, + height: widget.height, + child: FutureBuilder( + future: _futureComposition, + builder: (context, snapshot) { + if (snapshot.hasData) { + final composition = snapshot.data!; + return Lottie( + composition: composition, + controller: _controller, + renderCache: RenderCache.raster, + width: widget.width, + height: widget.height, + ); + } else { + return const SizedBox.shrink(); + } + }) + + // child: Lottie( + // composition: _composition, + // controller: _controller, + // renderCache: RenderCache.raster, + // width: widget.width, + // height: widget.height, + // ), + ), + ); + } +} diff --git a/lib/common/widgets/star_reward_widget.dart b/lib/common/widgets/star_reward_widget.dart index 7484acd..5b73766 100644 --- a/lib/common/widgets/star_reward_widget.dart +++ b/lib/common/widgets/star_reward_widget.dart @@ -68,7 +68,7 @@ class _StarRewardWidgetState extends State _futureComposition.then((composition) { Log.d("$TAG _futureComposition.then duration=${composition.duration}"); _controller.duration = composition.duration; - _controller.forward().whenComplete(() { + _controller.forward().whenCompleteOrCancel(() { setState(() { _isVisible = false; }); @@ -93,7 +93,7 @@ class _StarRewardWidgetState extends State assetPath = 'assets/lotties/star3_reward.zip'; break; } - return await AssetLottie('assets/lotties/reward.zip').load(); + return await AssetLottie(assetPath).load(); } @override diff --git a/lib/pages/practice/bloc/topic_picture_bloc.dart b/lib/pages/practice/bloc/topic_picture_bloc.dart index 43c73c6..da136cb 100644 --- a/lib/pages/practice/bloc/topic_picture_bloc.dart +++ b/lib/pages/practice/bloc/topic_picture_bloc.dart @@ -14,11 +14,14 @@ import 'package:wow_english/models/course_process_entity.dart'; import 'package:wow_english/pages/section/subsection/base_section/bloc.dart'; import 'package:wow_english/pages/section/subsection/base_section/event.dart'; import 'package:wow_english/pages/section/subsection/base_section/state.dart'; +import 'package:wow_english/utils/audio_player_util.dart'; import 'package:wow_english/utils/toast_util.dart'; import '../../../common/permission/permissionRequester.dart'; +import '../../../common/utils/click_with_music_controller.dart'; import '../../../common/utils/show_star_reward_dialog.dart'; import '../../../route/route.dart'; +import '../../../utils/log_util.dart'; part 'topic_picture_event.dart'; @@ -251,7 +254,10 @@ class TopicPictureBloc ///为空则数据异常,用于是否晃动时需要 bool? checkAnswerRight(int selectIndex) { CourseProcessTopics? topics = _entity?.topics?[_currentPage]; - if (topics == null || topics.topicAnswerList == null || selectIndex < 0 || selectIndex >= topics.topicAnswerList!.length) { + if (topics == null || + topics.topicAnswerList == null || + selectIndex < 0 || + selectIndex >= topics.topicAnswerList!.length) { return null; } CourseProcessTopicsTopicAnswerList? answerList = @@ -300,7 +306,29 @@ class TopicPictureBloc final Map args = event.message as Map; final result = args['result'] as Map; final overall = result['overall'].toString(); - showStarRewardDialog(context, starCount: _evaluateScore(overall)); + int score = int.parse(overall); + AudioPlayerUtilType audioPlayerUtilType; + String? lottieFile; + if (score > 90) { + audioPlayerUtilType = AudioPlayerUtilType.excellent; + lottieFile = 'assets/lotties/excellent.zip'; + } else if (score > 70) { + audioPlayerUtilType = AudioPlayerUtilType.great; + lottieFile = 'assets/lotties/great.zip'; + } else if (score > 50) { + audioPlayerUtilType = AudioPlayerUtilType.good; + lottieFile = 'assets/lotties/good.zip'; + } else { + audioPlayerUtilType = AudioPlayerUtilType.tryAgain; + } + if (lottieFile != null) { + showCheerRewardDialog(context, lottieFile: lottieFile); + } + Log.d("WQF audioPlayerUtilType=$audioPlayerUtilType lottieFile=$lottieFile"); + ClickWithMusicController.instance + .playMusicAndPerformAction(context, audioPlayerUtilType, () => { + ///todo 是否需要自动翻页 + }); // showToast('测评成功,分数是$overall', duration: const Duration(seconds: 5)); _isRecording = false; emitter(XSVoiceTestState()); @@ -364,7 +392,7 @@ class TopicPictureBloc _isResultSoundPlaying = true; _forbiddenWhenCorrect = isCorrect; if (isCorrect) { - await audioPlayer.play(AssetSource('correct_voice'.assetMp3)); + await audioPlayer.play(AssetSource('right'.assetMp3)); } else { await audioPlayer.play(AssetSource('wrong'.assetMp3)); } diff --git a/lib/pages/reading/reading_page.dart b/lib/pages/reading/reading_page.dart index c4becba..509968d 100644 --- a/lib/pages/reading/reading_page.dart +++ b/lib/pages/reading/reading_page.dart @@ -8,7 +8,7 @@ import 'package:wow_english/route/route.dart'; import '../../common/core/app_consts.dart'; import '../../common/core/user_util.dart'; -import '../../common/widgets/throttledGesture_gesture_detector.dart'; +import '../../common/widgets/recorder_widget.dart'; import '../../models/course_process_entity.dart'; import '../../utils/log_util.dart'; import 'bloc/reading_bloc.dart'; @@ -186,25 +186,22 @@ class _ReadingPage extends StatelessWidget { SizedBox( width: 10.w, ), - ThrottledGestureDetector( - throttleTime: 1000, - onTap: () { - if (bloc.isRecording) { - bloc.add(XSVoiceStopEvent()); - } else { - bloc.add(XSVoiceStartEvent( - bloc.readingContent(), - '0', - UserUtil.getUser()?.id.toString())); - } - }, - child: Image.asset( - bloc.isRecording - ? 'micro_phone'.assetGif - : 'micro_phone'.assetPng, - height: 47.h, - width: 47.w, - )), + RecorderWidget( + isPlaying: bloc.isRecording, + isClickable: bloc.voicePlayState != VoicePlayState.playing, + width: 60.w, + height: 60.w, + onTap: () { + if (bloc.isRecording) { + bloc.add(XSVoiceStopEvent()); + } else { + bloc.add(XSVoiceStartEvent( + bloc.readingContent(), + '0', + UserUtil.getUser()?.id.toString())); + } + }, + ), SizedBox( width: 10.w, ), diff --git a/lib/utils/audio_player_util.dart b/lib/utils/audio_player_util.dart index a08ece4..0ead8d3 100644 --- a/lib/utils/audio_player_util.dart +++ b/lib/utils/audio_player_util.dart @@ -14,7 +14,11 @@ enum AudioPlayerUtilType { quizTime('quiz_time'), countWithMe('count_with_me_instrumental'), inMyTummy('in_my_tummy_instrumental'), - touch('touch_instrumental'); + touch('touch_instrumental'), + excellent('excellent'), + great('great'), + good('good'), + tryAgain('try_again'); const AudioPlayerUtilType(this.path); -- libgit2 0.22.2