diff --git a/lib/common/utils/show_star_reward_dialog.dart b/lib/common/utils/show_star_reward_dialog.dart index e36de90..08cbdeb 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 'package:wow_english/common/widgets/star_reward_widget.dart'; + +import '../widgets/star_reward_widget.dart'; void showStarRewardDialog(BuildContext context, { double width = 200, @@ -13,7 +14,7 @@ void showStarRewardDialog(BuildContext context, { return Dialog( backgroundColor: Colors.transparent, // 设置对话框背景为透明 insetPadding: const EdgeInsets.all(0), // 去除对话框的内边距 - child: StarRewardAnimation( + child: StarRewardWidget( width: width, height: height, isPlaying: true, diff --git a/lib/common/widgets/record_playback_widget.dart b/lib/common/widgets/record_playback_widget.dart index 5f63226..018eff3 100644 --- a/lib/common/widgets/record_playback_widget.dart +++ b/lib/common/widgets/record_playback_widget.dart @@ -22,15 +22,15 @@ class _RecorderPlaybackWidget extends StatefulWidget { }) : super(key: key); @override - __RecorderPlaybackWidgetState createState() => - __RecorderPlaybackWidgetState(); + _RecorderPlaybackWidgetState createState() => + _RecorderPlaybackWidgetState(); } -class __RecorderPlaybackWidgetState extends State<_RecorderPlaybackWidget> +class _RecorderPlaybackWidgetState extends State<_RecorderPlaybackWidget> with SingleTickerProviderStateMixin { late final AnimationController _controller; late final LottieComposition _composition; - static const String TAG = "_RecorderPlaybackWidget"; + static const String TAG = "RecorderPlaybackWidget"; bool _isPlaying = false; diff --git a/lib/common/widgets/recorder_widget.dart b/lib/common/widgets/recorder_widget.dart index 1169b63..287a7d5 100644 --- a/lib/common/widgets/recorder_widget.dart +++ b/lib/common/widgets/recorder_widget.dart @@ -28,7 +28,7 @@ class RecorderWidget extends StatefulWidget { class _RecorderWidgetState extends State with SingleTickerProviderStateMixin { late final AnimationController _controller; - late final LottieComposition _composition; + late final Future _futureComposition; static const String TAG = "RecorderWidget"; bool _isPlaying = false; @@ -38,43 +38,60 @@ class _RecorderWidgetState extends State super.initState(); _controller = AnimationController(vsync: this); _loadComposition(); + if (widget.isPlaying) { + _playInitialAnimation(); + } _controller.addListener(() { // Log.d("$TAG addListener _controller=${_controller.status}"); }); } - Future _loadComposition() async { - final composition = await AssetLottie('assets/lotties/recorder_input.zip').load(); - setState(() { - _composition = composition; - _controller.duration = _composition.duration; - }); - - if (widget.isPlaying) { + @override + void didUpdateWidget(RecorderWidget oldWidget) { + super.didUpdateWidget(oldWidget); + Log.d( + "$TAG didUpdateWidget widget=${widget.isPlaying} oldWidget=${oldWidget.isPlaying} _isPlaying=$_isPlaying"); + if (widget.isPlaying && !_isPlaying) { + setState(() { + _isPlaying = true; + }); _playInitialAnimation(); + } else if (!widget.isPlaying && _isPlaying) { + _playFinalAnimation(); } } + Future _loadComposition() async { + _futureComposition = + AssetLottie('assets/lotties/recorder_input.zip').load(); + } + void _playInitialAnimation() { _controller.reset(); - _controller - .animateTo(22 / _composition.endFrame) - .whenComplete(() => _loopMiddleAnimation()); + _futureComposition.then((composition) { + _controller.duration = composition.duration; + _controller + .animateTo(22 / composition.endFrame) + .whenComplete(() => _loopMiddleAnimation(composition)); + }); } - void _loopMiddleAnimation() { + void _loopMiddleAnimation(LottieComposition composition) { _controller.repeat( - min: 22 / _composition.endFrame, - max: 37 / _composition.endFrame, + min: 22 / composition.endFrame, + max: 37 / composition.endFrame, ); } void _playFinalAnimation() { _controller.stop(); - _controller - .animateTo(50 / _composition.endFrame) - .whenComplete(() => _resetAnimation()); + _futureComposition.then((composition) { + _controller.duration = composition.duration; + _controller + .animateTo(50 / composition.endFrame) + .whenComplete(() => _resetAnimation()); + }); } void _resetAnimation() { @@ -85,20 +102,6 @@ class _RecorderWidgetState extends State } @override - void didUpdateWidget(RecorderWidget oldWidget) { - super.didUpdateWidget(oldWidget); - Log.d("$TAG didUpdateWidget widget=${widget.isPlaying} oldWidget=${oldWidget.isPlaying} _isPlaying=$_isPlaying"); - if (widget.isPlaying && !_isPlaying) { - setState(() { - _isPlaying = true; - }); - _playInitialAnimation(); - } else if (!widget.isPlaying && _isPlaying) { - _playFinalAnimation(); - } - } - - @override void dispose() { _controller.dispose(); super.dispose(); @@ -109,15 +112,23 @@ class _RecorderWidgetState extends State return ThrottledGestureDetector( onTap: widget.isClickable ? widget.onTap : null, child: Opacity( - opacity: widget.isClickable ? 1.0 : 0.5, // 设置透明度 - child: Lottie( - composition: _composition, - controller: _controller, - renderCache: RenderCache.raster, - width: widget.width, - height: widget.height, - ), - ), + opacity: widget.isClickable ? 1.0 : 0.5, // 设置透明度 + 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(); + } + })), ); } } diff --git a/lib/common/widgets/speaker_widget.dart b/lib/common/widgets/speaker_widget.dart index b969f02..80f3690 100644 --- a/lib/common/widgets/speaker_widget.dart +++ b/lib/common/widgets/speaker_widget.dart @@ -70,6 +70,8 @@ class _SpeakerWidgetState extends State } void _startAnimation() { + Log.d( + "$TAG _startAnimation widget=${widget.isPlaying} _isPlaying=$_isPlaying _controller.isAnimating=${_controller.isAnimating}"); _timer = Timer.periodic(const Duration(milliseconds: 300), (Timer timer) { setState(() { _currentFrame = ((_currentFrame + 1) % 3); @@ -78,9 +80,15 @@ class _SpeakerWidgetState extends State } void _stopAnimation() { + Log.d( + "$TAG _stopAnimation widget=${widget.isPlaying} _isPlaying=$_isPlaying _controller.isAnimating=${_controller.isAnimating}"); _timer?.cancel(); + _controller.stop(); + _controller.reset(); + setState(() { _isPlaying = false; + // _controller.value = 0; _currentFrame = 0; }); } diff --git a/lib/common/widgets/star_reward_widget.dart b/lib/common/widgets/star_reward_widget.dart index 215b2ed..7484acd 100644 --- a/lib/common/widgets/star_reward_widget.dart +++ b/lib/common/widgets/star_reward_widget.dart @@ -1,14 +1,16 @@ import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; -class StarRewardAnimation extends StatefulWidget { +import '../../utils/log_util.dart'; + +class StarRewardWidget extends StatefulWidget { final double width; final double height; final bool isPlaying; final int starCount; final VoidCallback onAnimationEnd; - const StarRewardAnimation({ + const StarRewardWidget({ Key? key, required this.width, required this.height, @@ -18,14 +20,15 @@ class StarRewardAnimation extends StatefulWidget { }) : super(key: key); @override - _StarRewardAnimationState createState() => _StarRewardAnimationState(); + _StarRewardWidgetState createState() => _StarRewardWidgetState(); } -class _StarRewardAnimationState extends State +class _StarRewardWidgetState extends State with SingleTickerProviderStateMixin { late final AnimationController _controller; - late final LottieComposition _composition; + late final Future _futureComposition; bool _isVisible = false; + static const String TAG = "StarRewardWidget"; @override void initState() { @@ -35,41 +38,42 @@ class _StarRewardAnimationState extends State } @override - void didUpdateWidget(StarRewardAnimation oldWidget) { + void didUpdateWidget(StarRewardWidget oldWidget) { super.didUpdateWidget(oldWidget); if (widget.isPlaying && !_controller.isAnimating) { _startAnimation(); } } - Future _loadComposition() async { + void _loadComposition() { // final composition = await AssetLottie('assets/lotties/recorder_input.zip').load(); // setState(() { // _composition = composition; // _controller.duration = _composition.duration; // }); - final composition = await _loadLottieComposition(); - setState(() { - _composition = composition; - _controller.duration = _composition.duration; - }); + _futureComposition = _loadLottieComposition(); if (widget.isPlaying) { - _startAnimation(); + _startAnimation(); } } - void _startAnimation() async { + void _startAnimation() { + Log.d("$TAG _startAnimation"); setState(() { _isVisible = true; }); - _controller.forward().whenComplete(() { - setState(() { - _isVisible = false; + _futureComposition.then((composition) { + Log.d("$TAG _futureComposition.then duration=${composition.duration}"); + _controller.duration = composition.duration; + _controller.forward().whenComplete(() { + setState(() { + _isVisible = false; + }); + widget.onAnimationEnd(); // 调用外部回调函数 }); - widget.onAnimationEnd(); // 调用外部回调函数 }); } @@ -89,7 +93,7 @@ class _StarRewardAnimationState extends State assetPath = 'assets/lotties/star3_reward.zip'; break; } - return await AssetLottie(assetPath).load(); + return await AssetLottie('assets/lotties/reward.zip').load(); } @override @@ -102,16 +106,33 @@ class _StarRewardAnimationState extends State Widget build(BuildContext context) { return Center( child: SizedBox( - width: widget.width, - height: widget.height, - child: Lottie( - composition: _composition, - controller: _controller, - renderCache: RenderCache.raster, 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/pages/practice/bloc/topic_picture_bloc.dart b/lib/pages/practice/bloc/topic_picture_bloc.dart index a413865..43c73c6 100644 --- a/lib/pages/practice/bloc/topic_picture_bloc.dart +++ b/lib/pages/practice/bloc/topic_picture_bloc.dart @@ -14,10 +14,10 @@ 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/loading.dart'; import 'package:wow_english/utils/toast_util.dart'; import '../../../common/permission/permissionRequester.dart'; +import '../../../common/utils/show_star_reward_dialog.dart'; import '../../../route/route.dart'; part 'topic_picture_event.dart'; @@ -239,6 +239,7 @@ class TopicPictureBloc _selectItem = event.selectIndex; if (checkAnswerRight(_selectItem) == true) { _playResultSound(true); + showStarRewardDialog(context); // showToast('恭喜你,答对啦!',duration: const Duration(seconds: 2)); } else { _playResultSound(false); @@ -299,7 +300,8 @@ class TopicPictureBloc final Map args = event.message as Map; final result = args['result'] as Map; final overall = result['overall'].toString(); - showToast('测评成功,分数是$overall', duration: const Duration(seconds: 5)); + showStarRewardDialog(context, starCount: _evaluateScore(overall)); + // showToast('测评成功,分数是$overall', duration: const Duration(seconds: 5)); _isRecording = false; emitter(XSVoiceTestState()); if (isLastPage()) { @@ -307,6 +309,24 @@ class TopicPictureBloc } } + /// 根据得分计算星星数 + int _evaluateScore(String scoreStr) { + try { + int score = int.parse(scoreStr); + if (score > 80) { + return 3; + } else if (score > 60) { + return 2; + } else { + return 1; + } + } catch (e) { + // 如果转换失败,可以返回一个默认值或抛出异常 + print('Error parsing score: $e'); + return 1; // 返回一个默认值表示错误 + } + } + // 暂时没用上 void _voicePlayStateChange(VoicePlayStateChangeEvent event, Emitter emitter) async { diff --git a/lib/pages/practice/widgets/shake_widget.dart b/lib/pages/practice/widgets/shake_widget.dart index 80ee342..1bc6ba7 100644 --- a/lib/pages/practice/widgets/shake_widget.dart +++ b/lib/pages/practice/widgets/shake_widget.dart @@ -26,6 +26,7 @@ class ShakeWidget extends StatefulWidget { class _ShakeWidgetState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; + static const String TAG = "ShakeWidget"; @override void initState() { @@ -67,10 +68,12 @@ class _ShakeWidgetState extends State with SingleTickerProviderStat @override void didUpdateWidget(covariant ShakeWidget oldWidget) { super.didUpdateWidget(oldWidget); - Log.d("WQF didUpdateWidget widget.shouldShake=${widget.shouldShake} oldWidget.shouldShake=${oldWidget.shouldShake}"); + Log.d("$TAG didUpdateWidget widget.shouldShake=${widget.shouldShake} oldWidget.shouldShake=${oldWidget.shouldShake} isAnimating=${_controller.isAnimating}"); // if (widget.shouldShake && !oldWidget.shouldShake) { - if (widget.shouldShake) { + if (widget.shouldShake && !_controller.isAnimating) { _controller.forward(from: 0.0); + } else if (!widget.shouldShake && _controller.isAnimating) { + _controller.stop(); } }