From 108643cdabf1b9c194a840cd38f9e00bc8d2381f Mon Sep 17 00:00:00 2001 From: BisratHailu Date: Fri, 1 May 2026 01:20:07 +0300 Subject: [PATCH] fix(learn_practice): Adjust learn practice user flow --- lib/services/voice_recorder_service.dart | 23 ++++++- lib/ui/common/enmus.dart | 1 + .../learn_practice_viewmodel.dart | 13 +++- .../interact_learn_practice_screen.dart | 61 ++++++++++++++----- .../learn_practice_results_wrapper.dart | 4 +- pubspec.yaml | 2 +- 6 files changed, 80 insertions(+), 24 deletions(-) diff --git a/lib/services/voice_recorder_service.dart b/lib/services/voice_recorder_service.dart index d4f97a6..6b4a58e 100644 --- a/lib/services/voice_recorder_service.dart +++ b/lib/services/voice_recorder_service.dart @@ -1,3 +1,8 @@ +import 'dart:io'; + +import 'package:image_picker/image_picker.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:stacked/stacked.dart'; import 'package:waveform_recorder/waveform_recorder.dart'; import 'package:yimaru_app/ui/common/enmus.dart'; @@ -37,6 +42,22 @@ class VoiceRecorderService with ListenableServiceMixin { final file = _waveController.file; print('RECORDED $file'); if (file == null) return null; - return file.path; + + await _saveRecordedAudio(file); + + // return file.path; + String? voice = await _saveRecordedAudio(file); + return voice; + } + + Future _saveRecordedAudio(XFile? file) async { + late File voice; + final voiceName = basename(file?.name ?? ''); + final Directory appDir = await getApplicationDocumentsDirectory(); + + final localImagePath = join(appDir.path, voiceName); + voice = File(localImagePath); + //voice.writeAsBytes(await file?.); + return voice.path; } } diff --git a/lib/ui/common/enmus.dart b/lib/ui/common/enmus.dart index 1276f5a..ccf4a8f 100644 --- a/lib/ui/common/enmus.dart +++ b/lib/ui/common/enmus.dart @@ -62,4 +62,5 @@ enum StateObjects { coursePracticeQuestion, coursePracticeQuestions, recordLearnPracticeAnswer, + finishLearnPracticeQuestion } diff --git a/lib/ui/views/learn_practice/learn_practice_viewmodel.dart b/lib/ui/views/learn_practice/learn_practice_viewmodel.dart index 364cc68..fec9c70 100644 --- a/lib/ui/views/learn_practice/learn_practice_viewmodel.dart +++ b/lib/ui/views/learn_practice/learn_practice_viewmodel.dart @@ -167,6 +167,7 @@ class LearnPracticeViewModel extends ReactiveViewModel { Future _playAudio( {required Map answer, required Voice voice}) async { if (voice == Voice.recorded) { + print(answer['recorded_voice_answer']); await _audioPlayerService.playLocal(answer['recorded_voice_answer']); } else { await _audioPlayerService.playUrl(answer['sample_voice_answer']); @@ -214,18 +215,24 @@ class LearnPracticeViewModel extends ReactiveViewModel { } Future nextQuestion( + {required int index, required LearnQuestion question}) async => + await runBusyFuture(_nextQuestion(index: index, question: question), + busyObject: StateObjects.finishLearnPracticeQuestion); + + Future _nextQuestion( {required int index, required LearnQuestion question}) async { await stopRecording(); _answers.add({ 'busy_object': question.id.toString(), 'sample_text_answer': question.audioCorrectAnswerText, 'sample_voice_answer': question.sampleAnswerVoicePrompt, - 'recorded_voice_answer': _voiceRecorderService.getRecordedAudio(), + 'recorded_voice_answer': await _voiceRecorderService.getRecordedAudio(), }); if (index != _questions.length) { _questionSetController.nextPage( - duration: const Duration(milliseconds: 350), - curve: Curves.easeInOutCubic); + curve: Curves.easeInOutCubic, + duration: const Duration(milliseconds: 350), + ); await playVoicePrompt(_questions[index]); } else { goTo(3); diff --git a/lib/ui/views/learn_practice/screens/interact_learn_practice_screen.dart b/lib/ui/views/learn_practice/screens/interact_learn_practice_screen.dart index dffd20d..2349d30 100644 --- a/lib/ui/views/learn_practice/screens/interact_learn_practice_screen.dart +++ b/lib/ui/views/learn_practice/screens/interact_learn_practice_screen.dart @@ -5,6 +5,7 @@ import 'package:stacked/stacked.dart'; import 'package:waveform_recorder/waveform_recorder.dart'; import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; import 'package:yimaru_app/ui/widgets/cancel_learn_practice_sheet.dart'; +import 'package:yimaru_app/ui/widgets/custom_circular_progress_indicator.dart'; import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart'; import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart'; import 'package:yimaru_app/ui/widgets/wave_wrapper.dart'; @@ -70,7 +71,6 @@ class InteractLearnPracticeScreen children: [ _buildBodyColumnWrapper(context: context, viewModel: viewModel), _buildProgressIndicatorState(viewModel), - _buildPageLoadingIndicatorState(viewModel) ], ); @@ -139,7 +139,9 @@ class InteractLearnPracticeScreen viewModel.player.state == PlayerState.playing ? _buildListeningLabel() : VoiceRecordingState.recording == viewModel.recordingState - ? _buildSpeakingLabel() + ? viewModel.busy(StateObjects.finishLearnPracticeQuestion) + ? const SizedBox(height: 20) + : _buildSpeakingLabel() : const SizedBox(height: 20); Widget _buildListeningLabel() => Text( @@ -149,7 +151,7 @@ class InteractLearnPracticeScreen ); Widget _buildSpeakingLabel() => Text( - 'You\'re is speaking...', + 'You\'re speaking...', style: style14P400, textAlign: TextAlign.center, ); @@ -158,8 +160,11 @@ class InteractLearnPracticeScreen WaveWrapper(height: 200, child: _buildSpinnerState(viewModel)); Widget _buildSpinnerState(LearnPracticeViewModel viewModel) => - viewModel.player.state == PlayerState.playing - ? _buildSpinner() + viewModel.busy(StateObjects.recordLearnPracticeAnswer) || + viewModel.busy(StateObjects.finishLearnPracticeQuestion) + ? Container() + : viewModel.player.state == PlayerState.playing + ? _buildSpinner() : VoiceRecordingState.recording == viewModel.recordingState && viewModel.waveController.isRecording ? _buildSpeakingSpinnerColumn(viewModel) @@ -184,7 +189,6 @@ class InteractLearnPracticeScreen Widget _buildSpeakingSpinner(LearnPracticeViewModel viewModel) => WaveformRecorder( height: 35, - onRecordingStopped: () {}, waveColor: kcPrimaryColor, durationTextStyle: style14P600, controller: viewModel.waveController, @@ -221,7 +225,7 @@ class InteractLearnPracticeScreen [ _buildActionLabel(), verticalSpaceMedium, - _buildButtonsRowWrapper(context: context, viewModel: viewModel), + _buildButtonRowContainer(context: context, viewModel: viewModel), verticalSpaceMedium, ]; @@ -231,6 +235,27 @@ class InteractLearnPracticeScreen textAlign: TextAlign.center, ); + Widget _buildButtonRowContainer( + {required BuildContext context, + required LearnPracticeViewModel viewModel}) => + SizedBox( + height: 75, + width: double.maxFinite, + child: _buildButtonRowState(context: context, viewModel: viewModel), + ); + + Widget _buildButtonRowState( + {required BuildContext context, + required LearnPracticeViewModel viewModel}) => + viewModel.busy(StateObjects.learnPracticeQuestion) || + viewModel.busy(StateObjects.finishLearnPracticeQuestion) + ? _buildProgressIndicator(kcPrimaryColor) + : _buildButtonsRowWrapper(context: context, viewModel: viewModel); + + Widget _buildProgressIndicator(Color color) => Center( + child: CustomCircularProgressIndicator(color: color), + ); + Widget _buildButtonsRowWrapper( {required BuildContext context, required LearnPracticeViewModel viewModel}) => @@ -268,7 +293,12 @@ class InteractLearnPracticeScreen ); Widget _buildMicButtonWrapper(LearnPracticeViewModel viewModel) => - Expanded(child: _buildMicButton(viewModel)); + Expanded(child: _buildMicButtonState(viewModel)); + + Widget _buildMicButtonState(LearnPracticeViewModel viewModel) => + viewModel.busy(StateObjects.recordLearnPracticeAnswer) + ? _buildProgressIndicator(kcPrimaryColor) + : _buildMicButton(viewModel); Widget _buildMicButton(LearnPracticeViewModel viewModel) => ElevatedButton( style: ButtonStyle( @@ -335,7 +365,11 @@ class InteractLearnPracticeScreen Widget _buildProgressIndicatorState(LearnPracticeViewModel viewModel) => viewModel.recordingState == VoiceRecordingState.pending - ? _buildProgressIndicatorWrapper(viewModel) + ? viewModel.busy(StateObjects.finishLearnPracticeQuestion) || + viewModel.busy(StateObjects.learnPracticeQuestion) || + viewModel.busy(StateObjects.recordLearnPracticeAnswer) + ? Container() + : _buildProgressIndicatorWrapper(viewModel) : Container(); Widget _buildProgressIndicatorWrapper(LearnPracticeViewModel viewModel) => @@ -349,17 +383,12 @@ class InteractLearnPracticeScreen Widget _buildProgressIndicatorSpacer(LearnPracticeViewModel viewModel) => Padding( padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildProgressIndicator(viewModel), + child: _buildLinearProgressIndicator(viewModel), ); - Widget _buildProgressIndicator(LearnPracticeViewModel viewModel) => + Widget _buildLinearProgressIndicator(LearnPracticeViewModel viewModel) => CustomLinearProgressIndicator( activeColor: kcPrimaryColor, progress: viewModel.progress, backgroundColor: kcVeryLightGrey); - - Widget _buildPageLoadingIndicatorState(LearnPracticeViewModel viewModel) => - viewModel.busy(StateObjects.learnPracticeQuestion) - ? const PageLoadingIndicator() - : Container(); } diff --git a/lib/ui/widgets/learn_practice_results_wrapper.dart b/lib/ui/widgets/learn_practice_results_wrapper.dart index 7c0fa76..4d00ecd 100644 --- a/lib/ui/widgets/learn_practice_results_wrapper.dart +++ b/lib/ui/widgets/learn_practice_results_wrapper.dart @@ -46,7 +46,5 @@ class LearnPracticeResultsWrapper itemBuilder: (context, index) => _buildResult(viewModel.answers[index]), ); - Widget _buildResult(Map answer) => LearnPracticeResultCard( - answer: answer, - ); + Widget _buildResult(Map answer) => LearnPracticeResultCard(answer: answer); } diff --git a/pubspec.yaml b/pubspec.yaml index 1303e20..2fdb46a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: yimaru_app -version: 0.1.9+11 +version: 0.1.10+12 publish_to: 'none' description: A new Flutter project.