Commit a506beff7f4ef20d2016b98e901c8aeda51af822
1 parent
608c05b4
feat:先声sdk方法找不到问题修复;绘本接口&逻辑
Showing
10 changed files
with
269 additions
and
81 deletions
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/MainActivity.kt
@@ -16,6 +16,8 @@ class MainActivity : FlutterActivity() { | @@ -16,6 +16,8 @@ class MainActivity : FlutterActivity() { | ||
16 | override fun onCreate(savedInstanceState: Bundle?) { | 16 | override fun onCreate(savedInstanceState: Bundle?) { |
17 | super.onCreate(savedInstanceState) | 17 | super.onCreate(savedInstanceState) |
18 | Log.i("WowEnglish", "MainActivity onCreate") | 18 | Log.i("WowEnglish", "MainActivity onCreate") |
19 | + | ||
20 | + flutterEngine?.let { SingSoungMethodChannel(this, it) } | ||
19 | } | 21 | } |
20 | 22 | ||
21 | override fun onResume() { | 23 | override fun onResume() { |
@@ -46,9 +48,4 @@ class MainActivity : FlutterActivity() { | @@ -46,9 +48,4 @@ class MainActivity : FlutterActivity() { | ||
46 | // 打开沉浸式 | 48 | // 打开沉浸式 |
47 | WindowCompat.setDecorFitsSystemWindows(window, false)*/ | 49 | WindowCompat.setDecorFitsSystemWindows(window, false)*/ |
48 | } | 50 | } |
49 | - | ||
50 | - override fun configureFlutterEngine(flutterEngine: FlutterEngine) { | ||
51 | - super.configureFlutterEngine(flutterEngine) | ||
52 | - SingSoungMethodChannel(this, flutterEngine) | ||
53 | - } | ||
54 | } | 51 | } |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/SingSoungMethodChannel.kt
1 | package com.kouyuxingqiu.wow_english.methodChannels | 1 | package com.kouyuxingqiu.wow_english.methodChannels |
2 | 2 | ||
3 | +import android.util.Log | ||
4 | +import com.kouyuxingqiu.wow_english.singsound.SingEngineHelper | ||
3 | import io.flutter.embedding.android.FlutterActivity | 5 | import io.flutter.embedding.android.FlutterActivity |
4 | import io.flutter.embedding.engine.FlutterEngine | 6 | import io.flutter.embedding.engine.FlutterEngine |
5 | import io.flutter.plugin.common.MethodChannel | 7 | import io.flutter.plugin.common.MethodChannel |
@@ -30,10 +32,18 @@ class SingSoungMethodChannel(val activity: FlutterActivity, val flutterEngine: F | @@ -30,10 +32,18 @@ class SingSoungMethodChannel(val activity: FlutterActivity, val flutterEngine: F | ||
30 | ) | 32 | ) |
31 | methodChannel?.setMethodCallHandler { call, result -> | 33 | methodChannel?.setMethodCallHandler { call, result -> |
32 | when (call.method) { | 34 | when (call.method) { |
33 | - "startRecord" -> { | ||
34 | - val jsonStr = call.arguments as? String ?: return@setMethodCallHandler | 35 | + "initVoiceSdk" -> { |
36 | + SingEngineHelper.init(activity) | ||
37 | + } | ||
38 | + "startVoice" -> { | ||
39 | + val paramMap = call.arguments as HashMap<String, String> | ||
40 | + Log.d("WQF", "SingSoungMethodChannel startVoice=${call.arguments.javaClass} paramMap=$paramMap") | ||
41 | + paramMap["word"]?.let { SingEngineHelper.startRecord(it) } | ||
35 | //do nothing | 42 | //do nothing |
36 | } | 43 | } |
44 | + "stopVoice" -> { | ||
45 | + Log.d("WQF", "SingSoungMethodChannel stopVoice") | ||
46 | + } | ||
37 | else -> { | 47 | else -> { |
38 | result.notImplemented() | 48 | result.notImplemented() |
39 | } | 49 | } |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/SingEngineHelper.kt
@@ -18,7 +18,7 @@ import org.json.JSONObject | @@ -18,7 +18,7 @@ import org.json.JSONObject | ||
18 | import java.util.* | 18 | import java.util.* |
19 | 19 | ||
20 | 20 | ||
21 | -class SingEngineHelper private constructor() : | 21 | +object SingEngineHelper : |
22 | AudioErrorCallback, EvalReturnRequestIdCallback, OnRealTimeResultListener { | 22 | AudioErrorCallback, EvalReturnRequestIdCallback, OnRealTimeResultListener { |
23 | 23 | ||
24 | private val TAG = "SingEngineManager" | 24 | private val TAG = "SingEngineManager" |
@@ -54,57 +54,59 @@ class SingEngineHelper private constructor() : | @@ -54,57 +54,59 @@ class SingEngineHelper private constructor() : | ||
54 | mListeners = mutableListOf() | 54 | mListeners = mutableListOf() |
55 | if (mSingEngine == null) { | 55 | if (mSingEngine == null) { |
56 | mSingEngine = SingEngine.newInstance(context) | 56 | mSingEngine = SingEngine.newInstance(context) |
57 | - } | ||
58 | - Thread { | ||
59 | - try { | ||
60 | - mSingEngine?.run { | ||
61 | - // 设置测评结果监听器 | ||
62 | - setListener(this@SingEngineHelper) | ||
63 | - // 设置录音器初始化错误的回调 | ||
64 | - setAudioErrorCallback(this@SingEngineHelper) | ||
65 | - setEvalReturnRequestIdCallback(this@SingEngineHelper) | 57 | + Thread { |
58 | + try { | ||
59 | + mSingEngine?.run { | ||
60 | + // 设置测评结果监听器 | ||
61 | + setListener(this@SingEngineHelper) | ||
62 | + // 设置录音器初始化错误的回调 | ||
63 | + setAudioErrorCallback(this@SingEngineHelper) | ||
64 | + setEvalReturnRequestIdCallback(this@SingEngineHelper) | ||
66 | // // 设置音频格式 | 65 | // // 设置音频格式 |
67 | // setAudioType(AudioTypeEnum.WAV) | 66 | // setAudioType(AudioTypeEnum.WAV) |
68 | - // 设置引擎类型。引擎类型(在线CLOUD、 离线NATIVE、混合AUTO),默认使用在线引擎。 | ||
69 | - setServerType(CoreProvideTypeEnum.CLOUD) | ||
70 | - // 设置log日志级别 | ||
71 | - setLogLevel(4) | ||
72 | - // 禁用实时音量返回 | ||
73 | - disableVolume() | ||
74 | - // 设置录音音频路径 | ||
75 | - wavPath = AiUtil.getFilesDir(context).path + "/userdata/sound_record/" | ||
76 | - // 设置是否开启 VAD 功能 | ||
77 | - setOpenVad(true, "vad.0.1.bin") | ||
78 | - //setOpenVad(false, null); | ||
79 | - // 设置 VAD 前置超时时间 | ||
80 | - setFrontVadTime(3000) | 67 | + // 设置引擎类型。引擎类型(在线CLOUD、 离线NATIVE、混合AUTO),默认使用在线引擎。 |
68 | + setServerType(CoreProvideTypeEnum.CLOUD) | ||
69 | + // 设置log日志级别 | ||
70 | + setLogLevel(4) | ||
71 | + // 禁用实时音量返回 | ||
72 | + disableVolume() | ||
73 | + // 设置录音音频路径 | ||
74 | + wavPath = AiUtil.getFilesDir(context).path + "/userdata/sound_record/" | ||
75 | + // 设置是否开启 VAD 功能 | ||
76 | + setOpenVad(true, "vad.0.1.bin") | ||
77 | + //setOpenVad(false, null); | ||
78 | + // 设置 VAD 前置超时时间 | ||
79 | + setFrontVadTime(3000) | ||
81 | // setServerTimeout(10000) | 80 | // setServerTimeout(10000) |
82 | - // 开启错误日志保存到本地,发生错误时文件中会保存到android/data/包名/files/SSError.txt中 | 81 | + // 开启错误日志保存到本地,发生错误时文件中会保存到android/data/包名/files/SSError.txt中 |
83 | // setOpenWriteLog(true) | 82 | // setOpenWriteLog(true) |
84 | - // 设置在线服务器地址和账号 | ||
85 | - setServerAPI("wss://api.cloud.ssapi.cn") | 83 | + // 设置在线服务器地址和账号 |
84 | + setServerAPI("wss://api.cloud.ssapi.cn") | ||
86 | // // 设置评测语言(针对离线评测) | 85 | // // 设置评测语言(针对离线评测) |
87 | // setOffLineSource(OffLineSourceEnum.SOURCE_EN) | 86 | // setOffLineSource(OffLineSourceEnum.SOURCE_EN) |
88 | - // 设置引擎初始化参数 | ||
89 | - setNewCfg( | ||
90 | - buildInitJson( | ||
91 | - SingSoundConfig.APPKEY, | ||
92 | - SingSoundConfig.SECERTKEY | 87 | + // 设置引擎初始化参数 |
88 | + setNewCfg( | ||
89 | + buildInitJson( | ||
90 | + SingSoundConfig.APPKEY, | ||
91 | + SingSoundConfig.SECERTKEY | ||
92 | + ) | ||
93 | ) | 93 | ) |
94 | - ) | ||
95 | - // 引擎初始化 | ||
96 | - createEngine() | ||
97 | - } | 94 | + // 引擎初始化 |
95 | + createEngine("1") | ||
98 | 96 | ||
99 | - getSymbolsMap() | ||
100 | - } catch (e: Exception) { | ||
101 | - e.printStackTrace() | ||
102 | - } | ||
103 | - }.start() | 97 | + Log.w(TAG, "createEngine") |
98 | + } | ||
99 | + | ||
100 | + getSymbolsMap() | ||
101 | + } catch (e: Exception) { | ||
102 | + e.printStackTrace() | ||
103 | + } | ||
104 | + }.start() | ||
105 | + } | ||
104 | } | 106 | } |
105 | 107 | ||
106 | // 开始语音评测 | 108 | // 开始语音评测 |
107 | - fun startRecord(originText: String, @EvalTargetType evalTargetType: Int?) { | 109 | + fun startRecord(originText: String, @EvalTargetType evalTargetType: Int? = EvalTargetType.SENTENCE) { |
108 | try { | 110 | try { |
109 | val request = JSONObject() | 111 | val request = JSONObject() |
110 | when (evalTargetType) { | 112 | when (evalTargetType) { |
lib/generated/json/course_process_entity.g.dart
@@ -38,9 +38,9 @@ Map<String, dynamic> $CourseProcessEntityToJson(CourseProcessEntity entity) { | @@ -38,9 +38,9 @@ Map<String, dynamic> $CourseProcessEntityToJson(CourseProcessEntity entity) { | ||
38 | 38 | ||
39 | CourseProcessReadings $CourseProcessReadingsFromJson(Map<String, dynamic> json) { | 39 | CourseProcessReadings $CourseProcessReadingsFromJson(Map<String, dynamic> json) { |
40 | final CourseProcessReadings courseProcessReadings = CourseProcessReadings(); | 40 | final CourseProcessReadings courseProcessReadings = CourseProcessReadings(); |
41 | - final String? auditUrl = jsonConvert.convert<String>(json['auditUrl']); | ||
42 | - if (auditUrl != null) { | ||
43 | - courseProcessReadings.auditUrl = auditUrl; | 41 | + final String? audioUrl = jsonConvert.convert<String>(json['audioUrl']); |
42 | + if (audioUrl != null) { | ||
43 | + courseProcessReadings.audioUrl = audioUrl; | ||
44 | } | 44 | } |
45 | final int? courseLessonId = jsonConvert.convert<int>(json['courseLessonId']); | 45 | final int? courseLessonId = jsonConvert.convert<int>(json['courseLessonId']); |
46 | if (courseLessonId != null) { | 46 | if (courseLessonId != null) { |
@@ -83,7 +83,7 @@ CourseProcessReadings $CourseProcessReadingsFromJson(Map<String, dynamic> json) | @@ -83,7 +83,7 @@ CourseProcessReadings $CourseProcessReadingsFromJson(Map<String, dynamic> json) | ||
83 | 83 | ||
84 | Map<String, dynamic> $CourseProcessReadingsToJson(CourseProcessReadings entity) { | 84 | Map<String, dynamic> $CourseProcessReadingsToJson(CourseProcessReadings entity) { |
85 | final Map<String, dynamic> data = <String, dynamic>{}; | 85 | final Map<String, dynamic> data = <String, dynamic>{}; |
86 | - data['auditUrl'] = entity.auditUrl; | 86 | + data['audioUrl'] = entity.audioUrl; |
87 | data['courseLessonId'] = entity.courseLessonId; | 87 | data['courseLessonId'] = entity.courseLessonId; |
88 | data['createTime'] = entity.createTime; | 88 | data['createTime'] = entity.createTime; |
89 | data['deleted'] = entity.deleted; | 89 | data['deleted'] = entity.deleted; |
lib/models/course_process_entity.dart
@@ -24,7 +24,7 @@ class CourseProcessEntity { | @@ -24,7 +24,7 @@ class CourseProcessEntity { | ||
24 | 24 | ||
25 | @JsonSerializable() | 25 | @JsonSerializable() |
26 | class CourseProcessReadings { | 26 | class CourseProcessReadings { |
27 | - String? auditUrl; | 27 | + String? audioUrl; |
28 | int? courseLessonId; | 28 | int? courseLessonId; |
29 | String? createTime; | 29 | String? createTime; |
30 | String? deleted; | 30 | String? deleted; |
lib/pages/practice/bloc/topic_picture_bloc.dart
@@ -142,7 +142,7 @@ class TopicPictureBloc extends Bloc<TopicPictureEvent, TopicPictureState> { | @@ -142,7 +142,7 @@ class TopicPictureBloc extends Bloc<TopicPictureEvent, TopicPictureState> { | ||
142 | void _voiceXsTest(XSVoiceTestEvent event,Emitter<TopicPictureState> emitter) async { | 142 | void _voiceXsTest(XSVoiceTestEvent event,Emitter<TopicPictureState> emitter) async { |
143 | EasyLoading.show(status: '录音中....'); | 143 | EasyLoading.show(status: '录音中....'); |
144 | methodChannel.invokeMethod( | 144 | methodChannel.invokeMethod( |
145 | - 'starVoice', | 145 | + 'startVoice', |
146 | {'word':event.testWord,'type':event.type,'userId':event.userId.toString()} | 146 | {'word':event.testWord,'type':event.type,'userId':event.userId.toString()} |
147 | ); | 147 | ); |
148 | _isVoicing = true; | 148 | _isVoicing = true; |
lib/pages/reading/bloc/reading_bloc.dart
1 | +import 'package:audioplayers/audioplayers.dart'; | ||
1 | import 'package:flutter/cupertino.dart'; | 2 | import 'package:flutter/cupertino.dart'; |
3 | +import 'package:flutter/foundation.dart'; | ||
4 | +import 'package:flutter/services.dart'; | ||
2 | import 'package:flutter_bloc/flutter_bloc.dart'; | 5 | import 'package:flutter_bloc/flutter_bloc.dart'; |
6 | +import 'package:flutter_easyloading/flutter_easyloading.dart'; | ||
3 | import 'package:wow_english/pages/reading/widgets/ReadingModeType.dart'; | 7 | import 'package:wow_english/pages/reading/widgets/ReadingModeType.dart'; |
4 | 8 | ||
9 | +import '../../../common/request/dao/listen_dao.dart'; | ||
10 | +import '../../../common/request/exception.dart'; | ||
11 | +import '../../../models/course_process_entity.dart'; | ||
12 | +import '../../../utils/loading.dart'; | ||
13 | + | ||
5 | part 'reading_event.dart'; | 14 | part 'reading_event.dart'; |
6 | part 'reading_state.dart'; | 15 | part 'reading_state.dart'; |
7 | 16 | ||
8 | class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | 17 | class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { |
9 | - | ||
10 | final PageController pageController; | 18 | final PageController pageController; |
11 | 19 | ||
12 | ///当前页索引 | 20 | ///当前页索引 |
@@ -19,26 +27,68 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -19,26 +27,68 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
19 | 27 | ||
20 | ReadingModeType get currentMode => _currentMode; | 28 | ReadingModeType get currentMode => _currentMode; |
21 | 29 | ||
30 | + CourseProcessEntity? _entity; | ||
31 | + | ||
32 | + CourseProcessEntity? get entity => _entity; | ||
33 | + | ||
34 | + ///正在评测 | ||
35 | + bool _isRecording = false; | ||
36 | + | ||
37 | + bool get isRecording => _isRecording; | ||
38 | + | ||
39 | + late MethodChannel methodChannel; | ||
40 | + | ||
41 | + late AudioPlayer audioPlayer; | ||
42 | + | ||
22 | ReadingPageBloc(this.pageController) : super(ReadingPageInitial()) { | 43 | ReadingPageBloc(this.pageController) : super(ReadingPageInitial()) { |
23 | on<CurrentPageIndexChangeEvent>(_pageControllerChange); | 44 | on<CurrentPageIndexChangeEvent>(_pageControllerChange); |
24 | on<CurrentModeChangeEvent>(_selectItemLoad); | 45 | on<CurrentModeChangeEvent>(_selectItemLoad); |
25 | // pageController.addListener(() { | 46 | // pageController.addListener(() { |
26 | // _currentPage = pageController.page!.round(); | 47 | // _currentPage = pageController.page!.round(); |
27 | // }); | 48 | // }); |
49 | + on<RequestDataEvent>(_requestData); | ||
50 | + on<ReadingPageEvent>((event, emit) { | ||
51 | + //音频播放器 | ||
52 | + audioPlayer = AudioPlayer(); | ||
53 | + audioPlayer.onPlayerStateChanged.listen((event) { | ||
54 | + if (event == PlayerState.completed) { | ||
55 | + if (kDebugMode) { | ||
56 | + print('绘本播放完成'); | ||
57 | + | ||
58 | + } | ||
59 | + } | ||
60 | + }); | ||
61 | + | ||
62 | + methodChannel = const MethodChannel('sing_sound_method_channel'); | ||
63 | + methodChannel.invokeMethod('initVoiceSdk',{}); | ||
64 | + methodChannel.setMethodCallHandler((call) async { | ||
65 | + if (call.method == 'voiceResult') {//评测结束 | ||
66 | + // add(XSVoiceResultEvent(call.arguments)); | ||
67 | + } | ||
68 | + }); | ||
69 | + }); | ||
70 | + on<PlayOriginalAudioEvent>(_playOriginalAudio); | ||
71 | + on<XSVoiceTestEvent>(_voiceXsTest); | ||
72 | + on<XSVoiceResultEvent>(_voiceXsResult); | ||
28 | } | 73 | } |
29 | 74 | ||
30 | @override | 75 | @override |
31 | Future<void> close() { | 76 | Future<void> close() { |
32 | pageController.dispose(); | 77 | pageController.dispose(); |
78 | + audioPlayer.release(); | ||
79 | + audioPlayer.dispose(); | ||
33 | return super.close(); | 80 | return super.close(); |
34 | } | 81 | } |
35 | 82 | ||
36 | - void _pageControllerChange(CurrentPageIndexChangeEvent event, Emitter<ReadingPageState> emitter) async { | 83 | + void _pageControllerChange(CurrentPageIndexChangeEvent event, |
84 | + Emitter<ReadingPageState> emitter) async { | ||
37 | _currentPage = event.pageIndex; | 85 | _currentPage = event.pageIndex; |
86 | + _playOriginVoice(null); | ||
38 | emitter(CurrentPageIndexState()); | 87 | emitter(CurrentPageIndexState()); |
39 | } | 88 | } |
40 | 89 | ||
41 | - void _selectItemLoad(CurrentModeChangeEvent event, Emitter<ReadingPageState> emitter) async { | 90 | + void _selectItemLoad( |
91 | + CurrentModeChangeEvent event, Emitter<ReadingPageState> emitter) async { | ||
42 | if (_currentMode == ReadingModeType.auto) { | 92 | if (_currentMode == ReadingModeType.auto) { |
43 | _currentMode = ReadingModeType.manual; | 93 | _currentMode = ReadingModeType.manual; |
44 | } else { | 94 | } else { |
@@ -46,4 +96,77 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | @@ -46,4 +96,77 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { | ||
46 | } | 96 | } |
47 | emitter(CurrentModeState()); | 97 | emitter(CurrentModeState()); |
48 | } | 98 | } |
99 | + | ||
100 | + ///请求数据 | ||
101 | + void _requestData( | ||
102 | + RequestDataEvent event, Emitter<ReadingPageState> emitter) async { | ||
103 | + try { | ||
104 | + await loading(() async { | ||
105 | + _entity = await ListenDao.process('1'); | ||
106 | + print("reading page entity: ${_entity!.toJson()}"); | ||
107 | + emitter(RequestDataState()); | ||
108 | + }); | ||
109 | + } catch (e) { | ||
110 | + if (e is ApiException) { | ||
111 | + EasyLoading.showToast(e.message ?? '请求失败,请检查网络连接'); | ||
112 | + } | ||
113 | + } | ||
114 | + } | ||
115 | + | ||
116 | + void _playOriginalAudio(PlayOriginalAudioEvent event, Emitter<ReadingPageState> emitter) async { | ||
117 | + print("_playOriginalAudio"); | ||
118 | + _playOriginVoice(event.url); | ||
119 | + } | ||
120 | + | ||
121 | + /// 播放绘本原音 | ||
122 | + void _playOriginVoice(String? audioUrl) async { | ||
123 | + audioPlayer.stop(); | ||
124 | + final readingData = currentPageData(); | ||
125 | + if (readingData?.audioUrl != null) { | ||
126 | + final urlStr = audioUrl ?? readingData?.audioUrl ?? ''; | ||
127 | + if (urlStr.isNotEmpty) { | ||
128 | + audioPlayer.play(UrlSource(urlStr)); | ||
129 | + } | ||
130 | + } | ||
131 | + } | ||
132 | + | ||
133 | + int dataCount() { | ||
134 | + // print("dataCount=${_entity?.readings?.length ?? 0}"); | ||
135 | + return _entity?.readings?.length ?? 0; | ||
136 | + } | ||
137 | + | ||
138 | + CourseProcessReadings? currentPageData() { | ||
139 | + return _entity?.readings?[_currentPage]; | ||
140 | + } | ||
141 | + | ||
142 | + ///先声测试 | ||
143 | + void _voiceXsTest(XSVoiceTestEvent event, Emitter<ReadingPageState> emitter) async { | ||
144 | + startRecord(event.content); | ||
145 | + emitter(XSVoiceTestState()); | ||
146 | + } | ||
147 | + | ||
148 | + void startRecord(String content) async { | ||
149 | + if (_isRecording == true) { | ||
150 | + return; | ||
151 | + } | ||
152 | + EasyLoading.show(status: '录音中....'); | ||
153 | + methodChannel.invokeMethod( | ||
154 | + 'startVoice', | ||
155 | + {'word':'how old are you','type':'0','userId':'1'} | ||
156 | + ); | ||
157 | + _isRecording = true; | ||
158 | + } | ||
159 | + | ||
160 | + void _voiceXsResult(XSVoiceResultEvent event,Emitter<ReadingPageState> emitter) async { | ||
161 | + final Map args = event.message as Map; | ||
162 | + final result = args['result'] as String; | ||
163 | + if (result == '1') { | ||
164 | + final overall = args['overall'].toString(); | ||
165 | + EasyLoading.showToast('测评成功,分数是$overall',duration: const Duration(seconds: 10)); | ||
166 | + } else { | ||
167 | + EasyLoading.showToast('测评失败',duration: const Duration(seconds: 10)); | ||
168 | + } | ||
169 | + _isRecording = false; | ||
170 | + emitter(XSVoiceTestState()); | ||
171 | + } | ||
49 | } | 172 | } |
lib/pages/reading/bloc/reading_event.dart
@@ -8,4 +8,33 @@ class CurrentPageIndexChangeEvent extends ReadingPageEvent { | @@ -8,4 +8,33 @@ class CurrentPageIndexChangeEvent extends ReadingPageEvent { | ||
8 | CurrentPageIndexChangeEvent(this.pageIndex); | 8 | CurrentPageIndexChangeEvent(this.pageIndex); |
9 | } | 9 | } |
10 | 10 | ||
11 | -class CurrentModeChangeEvent extends ReadingPageEvent {} | ||
12 | \ No newline at end of file | 11 | \ No newline at end of file |
12 | +class CurrentModeChangeEvent extends ReadingPageEvent {} | ||
13 | + | ||
14 | +///请求接口获取数据 | ||
15 | +class RequestDataEvent extends ReadingPageEvent {} | ||
16 | + | ||
17 | +///播放原音频 | ||
18 | +class PlayOriginalAudioEvent extends ReadingPageEvent { | ||
19 | + final String? url; | ||
20 | + PlayOriginalAudioEvent(this.url); | ||
21 | +} | ||
22 | + | ||
23 | +///初始化先声SDK | ||
24 | +class XSVoiceInitEvent extends ReadingPageEvent { | ||
25 | + final Map data; | ||
26 | + XSVoiceInitEvent(this.data); | ||
27 | +} | ||
28 | + | ||
29 | +///评测结果 | ||
30 | +class XSVoiceResultEvent extends ReadingPageEvent { | ||
31 | + final dynamic message; | ||
32 | + XSVoiceResultEvent(this.message); | ||
33 | +} | ||
34 | + | ||
35 | +///先声测试 | ||
36 | +class XSVoiceTestEvent extends ReadingPageEvent { | ||
37 | + final String content; | ||
38 | + final String type; | ||
39 | + final String userId; | ||
40 | + XSVoiceTestEvent(this.content,this.type,this.userId); | ||
41 | +} | ||
13 | \ No newline at end of file | 42 | \ No newline at end of file |
lib/pages/reading/bloc/reading_state.dart
@@ -9,3 +9,7 @@ class CurrentPageIndexState extends ReadingPageState {} | @@ -9,3 +9,7 @@ class CurrentPageIndexState extends ReadingPageState {} | ||
9 | 9 | ||
10 | /// 手动or自动播放 | 10 | /// 手动or自动播放 |
11 | class CurrentModeState extends ReadingPageState {} | 11 | class CurrentModeState extends ReadingPageState {} |
12 | + | ||
13 | +class RequestDataState extends ReadingPageState {} | ||
14 | + | ||
15 | +class XSVoiceTestState extends ReadingPageState {} |
lib/pages/reading/reading_page.dart
@@ -4,6 +4,8 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; | @@ -4,6 +4,8 @@ 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 | 6 | ||
7 | +import '../../common/core/user_util.dart'; | ||
8 | +import '../../models/course_process_entity.dart'; | ||
7 | import 'bloc/reading_bloc.dart'; | 9 | import 'bloc/reading_bloc.dart'; |
8 | 10 | ||
9 | class ReadingPage extends StatelessWidget { | 11 | class ReadingPage extends StatelessWidget { |
@@ -12,7 +14,7 @@ class ReadingPage extends StatelessWidget { | @@ -12,7 +14,7 @@ class ReadingPage extends StatelessWidget { | ||
12 | @override | 14 | @override |
13 | Widget build(BuildContext context) { | 15 | Widget build(BuildContext context) { |
14 | return BlocProvider( | 16 | return BlocProvider( |
15 | - create: (_) => ReadingPageBloc(PageController()), | 17 | + create: (_) => ReadingPageBloc(PageController())..add(RequestDataEvent()), |
16 | child: _ReadingPage(), | 18 | child: _ReadingPage(), |
17 | ); | 19 | ); |
18 | } | 20 | } |
@@ -22,7 +24,15 @@ class _ReadingPage extends StatelessWidget { | @@ -22,7 +24,15 @@ class _ReadingPage extends StatelessWidget { | ||
22 | @override | 24 | @override |
23 | Widget build(BuildContext context) { | 25 | Widget build(BuildContext context) { |
24 | return BlocListener<ReadingPageBloc, ReadingPageState>( | 26 | return BlocListener<ReadingPageBloc, ReadingPageState>( |
25 | - listener: (context, state) {}, | 27 | + listener: (context, state) { |
28 | + if (state is RequestDataState) { | ||
29 | + // context.read<TopicPictureBloc>().add(CurrentPageIndexChangeEvent(0)); | ||
30 | + print('reading RequestDataState=$state'); | ||
31 | + | ||
32 | + ///刷新页面 | ||
33 | + context.read<ReadingPageBloc>().add(CurrentPageIndexChangeEvent(0)); | ||
34 | + } | ||
35 | + }, | ||
26 | child: _readingPageView(), | 36 | child: _readingPageView(), |
27 | ); | 37 | ); |
28 | } | 38 | } |
@@ -36,13 +46,13 @@ class _ReadingPage extends StatelessWidget { | @@ -36,13 +46,13 @@ class _ReadingPage extends StatelessWidget { | ||
36 | child: Stack( | 46 | child: Stack( |
37 | children: [ | 47 | children: [ |
38 | PageView.builder( | 48 | PageView.builder( |
39 | - itemCount: 10, | 49 | + itemCount: bloc.dataCount(), |
40 | controller: bloc.pageController, | 50 | controller: bloc.pageController, |
41 | onPageChanged: (int index) { | 51 | onPageChanged: (int index) { |
42 | bloc.add(CurrentPageIndexChangeEvent(index)); | 52 | bloc.add(CurrentPageIndexChangeEvent(index)); |
43 | }, | 53 | }, |
44 | itemBuilder: (context, int index) { | 54 | itemBuilder: (context, int index) { |
45 | - return _readingPagerItem(); | 55 | + return _readingPagerItem(bloc.entity!.readings![index]); |
46 | }), | 56 | }), |
47 | Container( | 57 | Container( |
48 | color: Colors.transparent, | 58 | color: Colors.transparent, |
@@ -76,15 +86,14 @@ class _ReadingPage extends StatelessWidget { | @@ -76,15 +86,14 @@ class _ReadingPage extends StatelessWidget { | ||
76 | ), | 86 | ), |
77 | alignment: Alignment.center, | 87 | alignment: Alignment.center, |
78 | child: Text( | 88 | child: Text( |
79 | - '${bloc.currentPage}/10', | ||
80 | - | ||
81 | - ///todo 分母需要替换成数据数组长度 | 89 | + '${bloc.currentPage}/${bloc.dataCount()}', |
82 | style: TextStyle(fontSize: 20.sp, color: Colors.white), | 90 | style: TextStyle(fontSize: 20.sp, color: Colors.white), |
83 | ), | 91 | ), |
84 | ), | 92 | ), |
85 | 93 | ||
86 | Padding( | 94 | Padding( |
87 | - padding: EdgeInsets.only(right: 15.w + ScreenUtil().bottomBarHeight), | 95 | + padding: EdgeInsets.only( |
96 | + right: 15.w + ScreenUtil().bottomBarHeight), | ||
88 | child: GestureDetector( | 97 | child: GestureDetector( |
89 | onTap: () { | 98 | onTap: () { |
90 | bloc.add(CurrentModeChangeEvent()); | 99 | bloc.add(CurrentModeChangeEvent()); |
@@ -121,17 +130,26 @@ class _ReadingPage extends StatelessWidget { | @@ -121,17 +130,26 @@ class _ReadingPage extends StatelessWidget { | ||
121 | margin: EdgeInsets.symmetric(horizontal: 10.w), | 130 | margin: EdgeInsets.symmetric(horizontal: 10.w), |
122 | child: Row( | 131 | child: Row( |
123 | children: [ | 132 | children: [ |
124 | - Image.asset( | ||
125 | - 'voice'.assetPng, | ||
126 | - height: 40.h, | ||
127 | - width: 45.w, | 133 | + GestureDetector( |
134 | + onTap: () { | ||
135 | + if (bloc.isRecording) { | ||
136 | + return; | ||
137 | + } | ||
138 | + print("voice tap"); | ||
139 | + bloc.add(PlayOriginalAudioEvent(null)); | ||
140 | + }, | ||
141 | + child: Image.asset( | ||
142 | + 'voice'.assetPng, | ||
143 | + height: 40.h, | ||
144 | + width: 45.w, | ||
145 | + ), | ||
128 | ), | 146 | ), |
129 | SizedBox( | 147 | SizedBox( |
130 | width: 10.w, | 148 | width: 10.w, |
131 | ), | 149 | ), |
132 | Expanded( | 150 | Expanded( |
133 | child: Text( | 151 | child: Text( |
134 | - "HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld", | 152 | + bloc.currentPageData()?.word ?? '', |
135 | style: TextStyle( | 153 | style: TextStyle( |
136 | color: const Color(0xFF333333), fontSize: 21.sp), | 154 | color: const Color(0xFF333333), fontSize: 21.sp), |
137 | maxLines: 2, | 155 | maxLines: 2, |
@@ -140,11 +158,18 @@ class _ReadingPage extends StatelessWidget { | @@ -140,11 +158,18 @@ class _ReadingPage extends StatelessWidget { | ||
140 | SizedBox( | 158 | SizedBox( |
141 | width: 10.w, | 159 | width: 10.w, |
142 | ), | 160 | ), |
143 | - Image.asset( | ||
144 | - 'micro_phone'.assetPng, | ||
145 | - height: 47.h, | ||
146 | - width: 47.w, | ||
147 | - ), | 161 | + GestureDetector( |
162 | + onTap: () { | ||
163 | + if (bloc.isRecording) { | ||
164 | + return; | ||
165 | + } | ||
166 | + bloc.add(XSVoiceTestEvent(bloc.currentPageData()?.word??'', '0',UserUtil.getUser()!.id.toString())); | ||
167 | + }, | ||
168 | + child: Image.asset( | ||
169 | + 'micro_phone'.assetPng, | ||
170 | + height: 47.h, | ||
171 | + width: 47.w, | ||
172 | + )), | ||
148 | SizedBox( | 173 | SizedBox( |
149 | width: 10.w, | 174 | width: 10.w, |
150 | ), | 175 | ), |
@@ -169,14 +194,12 @@ class _ReadingPage extends StatelessWidget { | @@ -169,14 +194,12 @@ class _ReadingPage extends StatelessWidget { | ||
169 | ); | 194 | ); |
170 | }); | 195 | }); |
171 | 196 | ||
172 | - Widget _readingPagerItem() => | 197 | + Widget _readingPagerItem(CourseProcessReadings readings) => |
173 | BlocBuilder<ReadingPageBloc, ReadingPageState>(builder: (context, state) { | 198 | BlocBuilder<ReadingPageBloc, ReadingPageState>(builder: (context, state) { |
174 | return Stack( | 199 | return Stack( |
175 | children: [ | 200 | children: [ |
176 | - Image.network( | ||
177 | - 'https://img.liblibai.com/web/648331d5a2cb5.png?image_process=format,webp&x-oss-process=image/resize,w_2980,m_lfit/format,webp', | ||
178 | - height: double.infinity, | ||
179 | - width: double.infinity), | 201 | + Image.network(readings.picUrl ?? '', |
202 | + height: double.infinity, width: double.infinity), | ||
180 | ], | 203 | ], |
181 | ); | 204 | ); |
182 | }); | 205 | }); |