Commit 065022b77e5d916f521551d108d7eadd9a839516
1 parent
cc3bbe3d
feat:绘本评测反馈弹窗+原音播放&录音播放&开始录音互斥逻辑
Showing
13 changed files
with
242 additions
and
46 deletions
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/SingSoungMethodChannel.kt
@@ -61,10 +61,16 @@ class SingSoungMethodChannel(activity: FlutterActivity, flutterEngine: FlutterEn | @@ -61,10 +61,16 @@ class SingSoungMethodChannel(activity: FlutterActivity, flutterEngine: FlutterEn | ||
61 | SingEngineHelper.addOnResultListener(this) | 61 | SingEngineHelper.addOnResultListener(this) |
62 | } | 62 | } |
63 | 63 | ||
64 | - override fun onResult(jsonObject: JSONObject, evalType: Int?) { | 64 | + override fun onResult(map: Map<String, Any>, evalType: Int?) { |
65 | //先声回调在子线程,需要切换到主线程 | 65 | //先声回调在子线程,需要切换到主线程 |
66 | GlobalHandler.runOnMainThread { | 66 | GlobalHandler.runOnMainThread { |
67 | - invokeMethod("voiceResult", jsonObject.toString()) | 67 | + invokeMethod("voiceResult", map) |
68 | + } | ||
69 | + } | ||
70 | + | ||
71 | + override fun onRecordFail(code: Int, message: String) { | ||
72 | + GlobalHandler.runOnMainThread { | ||
73 | + invokeMethod("voiceFail", mapOf("code" to code, "message" to message)) | ||
68 | } | 74 | } |
69 | } | 75 | } |
70 | 76 |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/OnSingEngineLifecycles.kt
@@ -14,11 +14,18 @@ interface SingEngineLifecycles { | @@ -14,11 +14,18 @@ interface SingEngineLifecycles { | ||
14 | fun onRecordPlayOver() | 14 | fun onRecordPlayOver() |
15 | 15 | ||
16 | // 评测完成 | 16 | // 评测完成 |
17 | - fun onResult(jsonObject: JSONObject, @EvalTargetType evalType: Int? = EvalTargetType.SENTENCE) | 17 | + fun onResult(map: Map<String, Any>, @EvalTargetType evalType: Int? = EvalTargetType.SENTENCE) |
18 | 18 | ||
19 | // 取消评测 | 19 | // 取消评测 |
20 | fun onCancel() | 20 | fun onCancel() |
21 | 21 | ||
22 | + /** | ||
23 | + * 评测失败 | ||
24 | + * @param code 失败错误码 | ||
25 | + * @param message 失败错误信息 | ||
26 | + */ | ||
27 | + fun onRecordFail(code: Int, message: String) | ||
28 | + | ||
22 | 29 | ||
23 | abstract class OnSingEngineAdapter : SingEngineLifecycles { | 30 | abstract class OnSingEngineAdapter : SingEngineLifecycles { |
24 | override fun onRecordBegin() { | 31 | override fun onRecordBegin() { |
@@ -33,12 +40,16 @@ interface SingEngineLifecycles { | @@ -33,12 +40,16 @@ interface SingEngineLifecycles { | ||
33 | 40 | ||
34 | } | 41 | } |
35 | 42 | ||
36 | - override fun onResult(jsonObject: JSONObject, @EvalTargetType evalType: Int?) { | 43 | + override fun onResult(map: Map<String, Any>, @EvalTargetType evalType: Int?) { |
37 | 44 | ||
38 | } | 45 | } |
39 | 46 | ||
40 | override fun onCancel() { | 47 | override fun onCancel() { |
41 | 48 | ||
42 | } | 49 | } |
50 | + | ||
51 | + override fun onRecordFail(code: Int, message: String) { | ||
52 | + | ||
53 | + } | ||
43 | } | 54 | } |
44 | } | 55 | } |
45 | \ No newline at end of file | 56 | \ No newline at end of file |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/SingEngineHelper.kt
@@ -9,6 +9,7 @@ import com.kouyuxingqiu.wow_english.singsound.config.EvalTargetType | @@ -9,6 +9,7 @@ import com.kouyuxingqiu.wow_english.singsound.config.EvalTargetType | ||
9 | import com.kouyuxingqiu.wow_english.singsound.config.SingSoundConfig | 9 | import com.kouyuxingqiu.wow_english.singsound.config.SingSoundConfig |
10 | import com.kouyuxingqiu.wow_english.singsound.config.VoiceConfig | 10 | import com.kouyuxingqiu.wow_english.singsound.config.VoiceConfig |
11 | import com.kouyuxingqiu.wow_english.singsound.config.WordConfig | 11 | import com.kouyuxingqiu.wow_english.singsound.config.WordConfig |
12 | +import com.kouyuxingqiu.wow_english.singsound.util.JsonUtils.toMap | ||
12 | import com.xs.SingEngine | 13 | import com.xs.SingEngine |
13 | import com.xs.impl.AudioErrorCallback | 14 | import com.xs.impl.AudioErrorCallback |
14 | import com.xs.impl.EvalReturnRequestIdCallback | 15 | import com.xs.impl.EvalReturnRequestIdCallback |
@@ -279,11 +280,10 @@ object SingEngineHelper : | @@ -279,11 +280,10 @@ object SingEngineHelper : | ||
279 | */ | 280 | */ |
280 | override fun onResult(jsonObject: JSONObject) { | 281 | override fun onResult(jsonObject: JSONObject) { |
281 | Log.i(TAG, "onResult = $jsonObject") | 282 | Log.i(TAG, "onResult = $jsonObject") |
282 | - parseResult(jsonObject) | ||
283 | setTokenToCache(jsonObject) | 283 | setTokenToCache(jsonObject) |
284 | mListeners?.let { | 284 | mListeners?.let { |
285 | for (callback in it) { | 285 | for (callback in it) { |
286 | - callback.onResult(jsonObject, mCurEvalType) | 286 | + callback.onResult(toMap(jsonObject), mCurEvalType) |
287 | } | 287 | } |
288 | } | 288 | } |
289 | } | 289 | } |
@@ -376,6 +376,13 @@ object SingEngineHelper : | @@ -376,6 +376,13 @@ object SingEngineHelper : | ||
376 | */ | 376 | */ |
377 | override fun onEnd(resultBody: ResultBody) { | 377 | override fun onEnd(resultBody: ResultBody) { |
378 | Log.i(TAG, "onEnd resultBody=$resultBody") | 378 | Log.i(TAG, "onEnd resultBody=$resultBody") |
379 | + if (resultBody.code != 0) { | ||
380 | + mListeners?.let { | ||
381 | + for (callback in it) { | ||
382 | + callback.onRecordFail(resultBody.code, resultBody.message) | ||
383 | + } | ||
384 | + } | ||
385 | + } | ||
379 | } | 386 | } |
380 | 387 | ||
381 | override fun onGetEvalRequestId(p0: String?) { | 388 | override fun onGetEvalRequestId(p0: String?) { |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/util/JsonUtils.java
@@ -2,10 +2,21 @@ package com.kouyuxingqiu.wow_english.singsound.util; | @@ -2,10 +2,21 @@ package com.kouyuxingqiu.wow_english.singsound.util; | ||
2 | 2 | ||
3 | import android.util.Log; | 3 | import android.util.Log; |
4 | 4 | ||
5 | +import com.google.gson.Gson; | ||
6 | +import com.google.gson.JsonElement; | ||
7 | +import com.google.gson.JsonObject; | ||
8 | + | ||
5 | import org.json.JSONArray; | 9 | import org.json.JSONArray; |
6 | import org.json.JSONException; | 10 | import org.json.JSONException; |
7 | import org.json.JSONObject; | 11 | import org.json.JSONObject; |
8 | 12 | ||
13 | +import java.util.ArrayList; | ||
14 | +import java.util.HashMap; | ||
15 | +import java.util.Iterator; | ||
16 | +import java.util.List; | ||
17 | +import java.util.Map; | ||
18 | +import java.util.Set; | ||
19 | + | ||
9 | /** | 20 | /** |
10 | * Created by wangz on 2017/8/29. | 21 | * Created by wangz on 2017/8/29. |
11 | */ | 22 | */ |
@@ -84,4 +95,42 @@ public class JsonUtils { | @@ -84,4 +95,42 @@ public class JsonUtils { | ||
84 | return false; | 95 | return false; |
85 | } | 96 | } |
86 | } | 97 | } |
98 | + | ||
99 | + public static Map<String, Object> toMap(JSONObject jsonObject) throws JSONException { | ||
100 | + Map<String, Object> map = new HashMap<>(); | ||
101 | + Iterator<String> keysIterator = jsonObject.keys(); | ||
102 | + while (keysIterator.hasNext()) { | ||
103 | + String key = keysIterator.next(); | ||
104 | + Object value = jsonObject.get(key); | ||
105 | + if (value instanceof JSONObject) { | ||
106 | + value = toMap((JSONObject) value); | ||
107 | + } | ||
108 | + if (value instanceof JSONArray) { | ||
109 | + value = toList((JSONArray) value); | ||
110 | + } | ||
111 | + map.put(key, value); | ||
112 | + } | ||
113 | + return map; | ||
114 | + } | ||
115 | + | ||
116 | + public static List<Object> toList(JSONArray jsonArray) throws JSONException { | ||
117 | + List<Object> list = new ArrayList<>(); | ||
118 | + for (int i = 0; i < jsonArray.length(); i++) { | ||
119 | + Object value = jsonArray.get(i); | ||
120 | + if (value instanceof JSONObject || value instanceof JSONArray) { | ||
121 | + value = toObject(value); | ||
122 | + } | ||
123 | + list.add(value); | ||
124 | + } | ||
125 | + return list; | ||
126 | + } | ||
127 | + | ||
128 | + public static Object toObject(Object json) throws JSONException { | ||
129 | + if (json instanceof JSONObject) { | ||
130 | + return toMap((JSONObject) json); | ||
131 | + } else if (json instanceof JSONArray) { | ||
132 | + return toList((JSONArray) json); | ||
133 | + } | ||
134 | + return json; | ||
135 | + } | ||
87 | } | 136 | } |
assets/images/pic_very_good.webp
0 → 100644
No preview for this file type
assets/images/record_pause.webp
No preview for this file type
assets/images/record_play.webp
No preview for this file type
assets/images/text_very_good.webp
0 → 100644
No preview for this file type
lib/pages/reading/bloc/reading_bloc.dart
@@ -12,6 +12,8 @@ import '../../../models/course_process_entity.dart'; | @@ -12,6 +12,8 @@ import '../../../models/course_process_entity.dart'; | ||
12 | import '../../../utils/loading.dart'; | 12 | import '../../../utils/loading.dart'; |
13 | import 'dart:convert'; | 13 | import 'dart:convert'; |
14 | 14 | ||
15 | +import '../../../utils/log_util.dart'; | ||
16 | + | ||
15 | part 'reading_event.dart'; | 17 | part 'reading_event.dart'; |
16 | part 'reading_state.dart'; | 18 | part 'reading_state.dart'; |
17 | 19 | ||
@@ -38,7 +40,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -38,7 +40,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
38 | int _currentPage = 0; | 40 | int _currentPage = 0; |
39 | 41 | ||
40 | ///当前播放模式 | 42 | ///当前播放模式 |
41 | - ReadingModeType _currentMode = ReadingModeType.auto; | 43 | + ReadingModeType _currentMode = ReadingModeType.manual; |
42 | 44 | ||
43 | int get currentPage => _currentPage + 1; | 45 | int get currentPage => _currentPage + 1; |
44 | 46 | ||
@@ -88,10 +90,12 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -88,10 +90,12 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
88 | if (event == PlayerState.completed) { | 90 | if (event == PlayerState.completed) { |
89 | debugPrint('播放完成'); | 91 | debugPrint('播放完成'); |
90 | _voicePlayState = VoicePlayState.completed; | 92 | _voicePlayState = VoicePlayState.completed; |
93 | + _onAudioPlayComplete(); | ||
91 | } | 94 | } |
92 | if (event == PlayerState.stopped) { | 95 | if (event == PlayerState.stopped) { |
93 | debugPrint('播放结束'); | 96 | debugPrint('播放结束'); |
94 | _voicePlayState = VoicePlayState.stop; | 97 | _voicePlayState = VoicePlayState.stop; |
98 | + _onAudioPlayComplete(); | ||
95 | } | 99 | } |
96 | 100 | ||
97 | if (event == PlayerState.playing) { | 101 | if (event == PlayerState.playing) { |
@@ -108,6 +112,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -108,6 +112,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
108 | const MethodChannel('wow_english/sing_sound_method_channel'); | 112 | const MethodChannel('wow_english/sing_sound_method_channel'); |
109 | methodChannel.invokeMethod('initVoiceSdk', {}); //初始化评测 | 113 | methodChannel.invokeMethod('initVoiceSdk', {}); //初始化评测 |
110 | methodChannel.setMethodCallHandler((call) async { | 114 | methodChannel.setMethodCallHandler((call) async { |
115 | + Log.d("setMethodCallHandler method=${call.method} arguments=${call.arguments}"); | ||
111 | if (call.method == 'voiceResult') { | 116 | if (call.method == 'voiceResult') { |
112 | //评测结果 | 117 | //评测结果 |
113 | add(XSVoiceResultEvent(call.arguments)); | 118 | add(XSVoiceResultEvent(call.arguments)); |
@@ -119,6 +124,8 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -119,6 +124,8 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
119 | if (kDebugMode) { | 124 | if (kDebugMode) { |
120 | print('评测开始'); | 125 | print('评测开始'); |
121 | } | 126 | } |
127 | + _isRecording = true; | ||
128 | + add(OnXSVoiceStateChangeEvent()); | ||
122 | return; | 129 | return; |
123 | } | 130 | } |
124 | 131 | ||
@@ -127,6 +134,8 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -127,6 +134,8 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
127 | if (kDebugMode) { | 134 | if (kDebugMode) { |
128 | print('评测结束'); | 135 | print('评测结束'); |
129 | } | 136 | } |
137 | + _isRecording = false; | ||
138 | + add(OnXSVoiceStateChangeEvent()); | ||
130 | return; | 139 | return; |
131 | } | 140 | } |
132 | 141 | ||
@@ -143,6 +152,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -143,6 +152,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
143 | on<XSVoiceStartEvent>(_voiceXsStart); | 152 | on<XSVoiceStartEvent>(_voiceXsStart); |
144 | on<XSVoiceStopEvent>(_voiceXsStop); | 153 | on<XSVoiceStopEvent>(_voiceXsStop); |
145 | on<XSVoiceResultEvent>(_voiceXsResult); | 154 | on<XSVoiceResultEvent>(_voiceXsResult); |
155 | + on<OnXSVoiceStateChangeEvent>(_onVoiceXsStateChange); | ||
146 | 156 | ||
147 | on<PlayRecordAudioEvent>(_playRecordAudio); | 157 | on<PlayRecordAudioEvent>(_playRecordAudio); |
148 | } | 158 | } |
@@ -178,7 +188,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -178,7 +188,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
178 | try { | 188 | try { |
179 | await loading(() async { | 189 | await loading(() async { |
180 | _entity = await ListenDao.process(courseLessonId); | 190 | _entity = await ListenDao.process(courseLessonId); |
181 | - print("reading page entity: ${_entity!.toJson()}"); | 191 | + Log.d("reading page entity: ${_entity!.toJson()}"); |
182 | emitter(RequestDataState()); | 192 | emitter(RequestDataState()); |
183 | }); | 193 | }); |
184 | } catch (e) { | 194 | } catch (e) { |
@@ -194,11 +204,20 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -194,11 +204,20 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
194 | _playOriginalAudioInner(event.url); | 204 | _playOriginalAudioInner(event.url); |
195 | } | 205 | } |
196 | 206 | ||
197 | - void _playOriginalAudioInner(String? audioUrl) { | ||
198 | - print("_playOriginalAudio url=$audioUrl"); | ||
199 | - audioUrl ??= currentPageData()?.audioUrl ?? ''; | ||
200 | - _playAudio(audioUrl); | ||
201 | - _isOriginAudioPlaying = true; | 207 | + ///播放原音音频 |
208 | + Future<void> _playOriginalAudioInner(String? audioUrl) async { | ||
209 | + if (_isRecordAudioPlaying) { | ||
210 | + _isRecordAudioPlaying = false; | ||
211 | + } | ||
212 | + Log.d("_playOriginalAudio _isRecordAudioPlaying=$_isRecordAudioPlaying _isOriginAudioPlaying=$_isOriginAudioPlaying url=$audioUrl"); | ||
213 | + if (_isOriginAudioPlaying) { | ||
214 | + _isOriginAudioPlaying = false; | ||
215 | + await audioPlayer.stop(); | ||
216 | + } else { | ||
217 | + _isOriginAudioPlaying = true; | ||
218 | + audioUrl ??= currentPageData()?.audioUrl ?? ''; | ||
219 | + _playAudio(audioUrl); | ||
220 | + } | ||
202 | } | 221 | } |
203 | 222 | ||
204 | /// 播放录音 | 223 | /// 播放录音 |
@@ -207,22 +226,29 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -207,22 +226,29 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
207 | _playRecordAudioInner(); | 226 | _playRecordAudioInner(); |
208 | } | 227 | } |
209 | 228 | ||
210 | - void _playRecordAudioInner() { | ||
211 | - final recordAudioUrl = currentPageData()?.recordUrl; | ||
212 | - print("_playRecordAudioInner url=${currentPageData()?.recordUrl}"); | ||
213 | - _playAudio(recordAudioUrl); | ||
214 | - _isRecordAudioPlaying = true; | 229 | + Future<void> _playRecordAudioInner() async { |
230 | + if (_isOriginAudioPlaying) { | ||
231 | + _isOriginAudioPlaying = false; | ||
232 | + } | ||
233 | + Log.d("_playRecordAudioInner _isRecordAudioPlaying=$_isRecordAudioPlaying url=${currentPageData()?.recordUrl}"); | ||
234 | + if (_isRecordAudioPlaying) { | ||
235 | + _isRecordAudioPlaying = false; | ||
236 | + await audioPlayer.stop(); | ||
237 | + } else { | ||
238 | + _isRecordAudioPlaying = true; | ||
239 | + final recordAudioUrl = currentPageData()?.recordUrl; | ||
240 | + _playAudio(recordAudioUrl); | ||
241 | + } | ||
242 | + // emit(VoicePlayStateChange()); | ||
215 | } | 243 | } |
216 | 244 | ||
217 | void _playAudio(String? audioUrl) async { | 245 | void _playAudio(String? audioUrl) async { |
218 | - await audioPlayer.stop(); | ||
219 | if (audioUrl!.isNotEmpty) { | 246 | if (audioUrl!.isNotEmpty) { |
220 | await audioPlayer.play(UrlSource(audioUrl)); | 247 | await audioPlayer.play(UrlSource(audioUrl)); |
221 | } | 248 | } |
222 | } | 249 | } |
223 | 250 | ||
224 | int dataCount() { | 251 | int dataCount() { |
225 | - // print("dataCount=${_entity?.readings?.length ?? 0}"); | ||
226 | return _entity?.readings?.length ?? 0; | 252 | return _entity?.readings?.length ?? 0; |
227 | } | 253 | } |
228 | 254 | ||
@@ -230,6 +256,18 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -230,6 +256,18 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
230 | return _entity?.readings?[_currentPage]; | 256 | return _entity?.readings?[_currentPage]; |
231 | } | 257 | } |
232 | 258 | ||
259 | + void nextPage() { | ||
260 | + if (_currentPage >= dataCount() - 1) { | ||
261 | + ///todo 最后一页了 | ||
262 | + } else { | ||
263 | + _currentPage += 1; | ||
264 | + pageController.nextPage( | ||
265 | + duration: const Duration(milliseconds: 500), | ||
266 | + curve: Curves.ease, | ||
267 | + ); | ||
268 | + } | ||
269 | + } | ||
270 | + | ||
233 | ///初始化SDK | 271 | ///初始化SDK |
234 | _initVoiceSdk( | 272 | _initVoiceSdk( |
235 | XSVoiceInitEvent event, Emitter<ReadingPageState> emitter) async { | 273 | XSVoiceInitEvent event, Emitter<ReadingPageState> emitter) async { |
@@ -241,7 +279,6 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -241,7 +279,6 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
241 | XSVoiceStartEvent event, Emitter<ReadingPageState> emitter) async { | 279 | XSVoiceStartEvent event, Emitter<ReadingPageState> emitter) async { |
242 | _stopAudio(); | 280 | _stopAudio(); |
243 | startRecord(event.content); | 281 | startRecord(event.content); |
244 | - emitter(XSVoiceTestState()); | ||
245 | } | 282 | } |
246 | 283 | ||
247 | void startRecord(String content) async { | 284 | void startRecord(String content) async { |
@@ -249,27 +286,22 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -249,27 +286,22 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
249 | return; | 286 | return; |
250 | } | 287 | } |
251 | methodChannel.invokeMethod( | 288 | methodChannel.invokeMethod( |
252 | - 'startVoice', {'word': 'how old are you', 'type': '0', 'userId': '1'}); | ||
253 | - _isRecording = true; | 289 | + 'startVoice', {'word': content, 'type': '0', 'userId': '1'}); |
254 | } | 290 | } |
255 | 291 | ||
256 | void _voiceXsResult( | 292 | void _voiceXsResult( |
257 | XSVoiceResultEvent event, Emitter<ReadingPageState> emitter) async { | 293 | XSVoiceResultEvent event, Emitter<ReadingPageState> emitter) async { |
258 | - final Map args = json.decode(event.message); | ||
259 | - final result = args['result']; | ||
260 | - print("_voiceXsResult result=${result}"); | ||
261 | - if (result != null) { | ||
262 | - final overall = result['overall'].toString(); | ||
263 | - EasyLoading.showToast('测评成功,分数是$overall', | ||
264 | - duration: const Duration(seconds: 10)); | ||
265 | - currentPageData()?.recordScore = overall; | ||
266 | - currentPageData()?.recordUrl = args['audioUrl'] + '.mp3'; | ||
267 | - _playRecordAudioInner(); | ||
268 | - } else { | ||
269 | - EasyLoading.showToast('测评失败', duration: const Duration(seconds: 10)); | ||
270 | - } | ||
271 | - _isRecording = false; | ||
272 | - emitter(XSVoiceTestState()); | 294 | + final Map args = event.message as Map; |
295 | + final result = args['result'] as Map; | ||
296 | + Log.d("_voiceXsResult result=$result"); | ||
297 | + final overall = result['overall'].toString(); | ||
298 | + EasyLoading.showToast('测评成功,分数是$overall', | ||
299 | + duration: const Duration(seconds: 10)); | ||
300 | + currentPageData()?.recordScore = overall; | ||
301 | + currentPageData()?.recordUrl = args['audioUrl'] + '.mp3'; | ||
302 | + ///完成录音后紧接着播放录音 | ||
303 | + _playRecordAudioInner(); | ||
304 | + emitter(FeedbackState()); | ||
273 | } | 305 | } |
274 | 306 | ||
275 | ///终止评测 | 307 | ///终止评测 |
@@ -283,9 +315,32 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -283,9 +315,32 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
283 | emitter(VoicePlayStateChange()); | 315 | emitter(VoicePlayStateChange()); |
284 | } | 316 | } |
285 | 317 | ||
318 | + void _onAudioPlayComplete() { | ||
319 | + if (_isRecordAudioPlaying && _currentMode == ReadingModeType.auto) { | ||
320 | + nextPage(); | ||
321 | + } | ||
322 | + | ||
323 | + Log.d("_onAudioPlayComplete _isOriginAudioPlaying=${_isOriginAudioPlaying} _voicePlayState=$_voicePlayState recordUrl=${currentPageData()?.recordUrl?.isNotEmpty}"); | ||
324 | + if (_isOriginAudioPlaying && _voicePlayState == VoicePlayState.completed && currentPageData()?.recordUrl?.isNotEmpty != true) { | ||
325 | + ///如果刚刚完成原音播放&&录音为空,则开始录音 | ||
326 | + startRecord(currentPageData()?.word ?? ''); | ||
327 | + } | ||
328 | + | ||
329 | + _isOriginAudioPlaying = false; | ||
330 | + _isRecordAudioPlaying = false; | ||
331 | + } | ||
332 | + | ||
286 | void _stopAudio() async { | 333 | void _stopAudio() async { |
287 | await audioPlayer.stop(); | 334 | await audioPlayer.stop(); |
288 | _isOriginAudioPlaying = false; | 335 | _isOriginAudioPlaying = false; |
289 | _isRecordAudioPlaying = false; | 336 | _isRecordAudioPlaying = false; |
290 | } | 337 | } |
338 | + | ||
339 | + void _onVoiceXsStateChange( | ||
340 | + OnXSVoiceStateChangeEvent event, | ||
341 | + Emitter<ReadingPageState> emitter | ||
342 | + ) async { | ||
343 | + emit(XSVoiceTestState()); | ||
344 | + } | ||
291 | } | 345 | } |
346 | + |
lib/pages/reading/bloc/reading_event.dart
@@ -45,6 +45,9 @@ class XSVoiceStartEvent extends ReadingPageEvent { | @@ -45,6 +45,9 @@ class XSVoiceStartEvent extends ReadingPageEvent { | ||
45 | ///先声评测停止 | 45 | ///先声评测停止 |
46 | class XSVoiceStopEvent extends ReadingPageEvent {} | 46 | class XSVoiceStopEvent extends ReadingPageEvent {} |
47 | 47 | ||
48 | +///先声评测状态 | ||
49 | +class OnXSVoiceStateChangeEvent extends ReadingPageEvent {} | ||
50 | + | ||
48 | ///音频播放状态 | 51 | ///音频播放状态 |
49 | class VoicePlayStateChangeEvent extends ReadingPageEvent {} | 52 | class VoicePlayStateChangeEvent extends ReadingPageEvent {} |
50 | 53 |
lib/pages/reading/bloc/reading_state.dart
@@ -15,3 +15,6 @@ class RequestDataState extends ReadingPageState {} | @@ -15,3 +15,6 @@ class RequestDataState extends ReadingPageState {} | ||
15 | class XSVoiceTestState extends ReadingPageState {} | 15 | class XSVoiceTestState extends ReadingPageState {} |
16 | 16 | ||
17 | class VoicePlayStateChange extends ReadingPageState {} | 17 | class VoicePlayStateChange extends ReadingPageState {} |
18 | + | ||
19 | +///评测结束反馈弹窗 | ||
20 | +class FeedbackState extends ReadingPageState {} |
lib/pages/reading/reading_page.dart
@@ -3,9 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; | @@ -3,9 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; | ||
3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; | 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; |
4 | import 'package:wow_english/common/extension/string_extension.dart'; | 4 | import 'package:wow_english/common/extension/string_extension.dart'; |
5 | import 'package:wow_english/pages/reading/widgets/ReadingModeType.dart'; | 5 | import 'package:wow_english/pages/reading/widgets/ReadingModeType.dart'; |
6 | +import 'package:wow_english/pages/reading/widgets/reading_dialog_widget.dart'; | ||
6 | 7 | ||
7 | import '../../common/core/user_util.dart'; | 8 | import '../../common/core/user_util.dart'; |
8 | import '../../models/course_process_entity.dart'; | 9 | import '../../models/course_process_entity.dart'; |
10 | +import '../../utils/log_util.dart'; | ||
9 | import 'bloc/reading_bloc.dart'; | 11 | import 'bloc/reading_bloc.dart'; |
10 | 12 | ||
11 | class ReadingPage extends StatelessWidget { | 13 | class ReadingPage extends StatelessWidget { |
@@ -29,21 +31,26 @@ class _ReadingPage extends StatelessWidget { | @@ -29,21 +31,26 @@ class _ReadingPage extends StatelessWidget { | ||
29 | Widget build(BuildContext context) { | 31 | Widget build(BuildContext context) { |
30 | return BlocListener<ReadingPageBloc, ReadingPageState>( | 32 | return BlocListener<ReadingPageBloc, ReadingPageState>( |
31 | listener: (context, state) { | 33 | listener: (context, state) { |
34 | + Log.d('reading BlocListener=$state'); | ||
32 | if (state is RequestDataState) { | 35 | if (state is RequestDataState) { |
33 | - // context.read<TopicPictureBloc>().add(CurrentPageIndexChangeEvent(0)); | ||
34 | - print('reading RequestDataState=$state'); | ||
35 | - | ||
36 | ///刷新页面 | 36 | ///刷新页面 |
37 | context.read<ReadingPageBloc>().add(CurrentPageIndexChangeEvent(0)); | 37 | context.read<ReadingPageBloc>().add(CurrentPageIndexChangeEvent(0)); |
38 | } | 38 | } |
39 | + if (state is FeedbackState) { | ||
40 | + showDialog<ReadingDialog>( | ||
41 | + context: context, | ||
42 | + barrierDismissible: false, | ||
43 | + builder: (context) { | ||
44 | + return const ReadingDialog(); | ||
45 | + }); | ||
46 | + } | ||
39 | }, | 47 | }, |
40 | child: _readingPageView(), | 48 | child: _readingPageView(), |
41 | ); | 49 | ); |
42 | } | 50 | } |
43 | 51 | ||
44 | Widget _readingPageView() => BlocBuilder<ReadingPageBloc, ReadingPageState>( | 52 | Widget _readingPageView() => BlocBuilder<ReadingPageBloc, ReadingPageState>( |
45 | - buildWhen: (_, s) => s is CurrentPageIndexState, | ||
46 | - builder: (context, state) { | 53 | + builder: (context, state) { |
47 | final bloc = BlocProvider.of<ReadingPageBloc>(context); | 54 | final bloc = BlocProvider.of<ReadingPageBloc>(context); |
48 | return Container( | 55 | return Container( |
49 | color: Colors.white, | 56 | color: Colors.white, |
@@ -176,7 +183,9 @@ class _ReadingPage extends StatelessWidget { | @@ -176,7 +183,9 @@ class _ReadingPage extends StatelessWidget { | ||
176 | } | 183 | } |
177 | }, | 184 | }, |
178 | child: Image.asset( | 185 | child: Image.asset( |
179 | - 'micro_phone'.assetPng, | 186 | + bloc.isRecording |
187 | + ? 'micro_phone'.assetGif | ||
188 | + : 'micro_phone'.assetPng, | ||
180 | height: 47.h, | 189 | height: 47.h, |
181 | width: 47.w, | 190 | width: 47.w, |
182 | )), | 191 | )), |
@@ -213,8 +222,10 @@ class _ReadingPage extends StatelessWidget { | @@ -213,8 +222,10 @@ class _ReadingPage extends StatelessWidget { | ||
213 | BlocBuilder<ReadingPageBloc, ReadingPageState>(builder: (context, state) { | 222 | BlocBuilder<ReadingPageBloc, ReadingPageState>(builder: (context, state) { |
214 | return Stack( | 223 | return Stack( |
215 | children: [ | 224 | children: [ |
216 | - Image.network(readings.picUrl ?? '', | ||
217 | - height: double.infinity, width: double.infinity), | 225 | + Positioned.fill( |
226 | + child: | ||
227 | + Image.network(readings.picUrl ?? '', fit: BoxFit.cover), | ||
228 | + ), | ||
218 | ], | 229 | ], |
219 | ); | 230 | ); |
220 | }); | 231 | }); |
lib/pages/reading/widgets/reading_dialog_widget.dart
0 → 100644
1 | +import 'dart:async'; | ||
2 | + | ||
3 | +import 'package:flutter/material.dart'; | ||
4 | +import 'package:flutter_screenutil/flutter_screenutil.dart'; | ||
5 | +import 'package:wow_english/common/extension/string_extension.dart'; | ||
6 | + | ||
7 | +///评测结束反馈弹窗 | ||
8 | +class ReadingDialog extends Dialog { | ||
9 | + | ||
10 | + const ReadingDialog({super.key}); | ||
11 | + | ||
12 | + //定时器,自动关闭Diolog | ||
13 | + _showTimer(context) { | ||
14 | + Timer.periodic(const Duration(milliseconds: 2000), //2000毫秒就是三秒 | ||
15 | + (t) { | ||
16 | + Navigator.pop(context); | ||
17 | + t.cancel(); //取消定时器 timer.cancel(); | ||
18 | + }); | ||
19 | + } | ||
20 | + | ||
21 | + @override | ||
22 | + Widget build(BuildContext context) { | ||
23 | + _showTimer(context); | ||
24 | + return Material( | ||
25 | + type: MaterialType.transparency, | ||
26 | + child: Center( | ||
27 | + child: Container( | ||
28 | + width: 250, | ||
29 | + height: double.infinity, | ||
30 | + color: Colors.transparent, | ||
31 | + child: Column( | ||
32 | + crossAxisAlignment: CrossAxisAlignment.center, | ||
33 | + mainAxisAlignment: MainAxisAlignment.center, | ||
34 | + children: [ | ||
35 | + Image.asset( | ||
36 | + 'text_very_good'.assetWebp, | ||
37 | + width: 237.w, | ||
38 | + height: 42.h, | ||
39 | + ), | ||
40 | + Image.asset( | ||
41 | + 'pic_very_good'.assetWebp, | ||
42 | + width: 210.w, | ||
43 | + height: 228.h, | ||
44 | + ), | ||
45 | + ], | ||
46 | + ), | ||
47 | + ), | ||
48 | + ), | ||
49 | + ); | ||
50 | + } | ||
51 | +} |