fix(learn_practice): Adjust learn practice user flow

This commit is contained in:
BisratHailu 2026-05-01 01:20:07 +03:00
parent 5cfe6897c6
commit 108643cdab
6 changed files with 80 additions and 24 deletions

View File

@ -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:stacked/stacked.dart';
import 'package:waveform_recorder/waveform_recorder.dart'; import 'package:waveform_recorder/waveform_recorder.dart';
import 'package:yimaru_app/ui/common/enmus.dart'; import 'package:yimaru_app/ui/common/enmus.dart';
@ -37,6 +42,22 @@ class VoiceRecorderService with ListenableServiceMixin {
final file = _waveController.file; final file = _waveController.file;
print('RECORDED $file'); print('RECORDED $file');
if (file == null) return null; if (file == null) return null;
return file.path;
await _saveRecordedAudio(file);
// return file.path;
String? voice = await _saveRecordedAudio(file);
return voice;
}
Future<String?> _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;
} }
} }

View File

@ -62,4 +62,5 @@ enum StateObjects {
coursePracticeQuestion, coursePracticeQuestion,
coursePracticeQuestions, coursePracticeQuestions,
recordLearnPracticeAnswer, recordLearnPracticeAnswer,
finishLearnPracticeQuestion
} }

View File

@ -167,6 +167,7 @@ class LearnPracticeViewModel extends ReactiveViewModel {
Future<void> _playAudio( Future<void> _playAudio(
{required Map<String, dynamic> answer, required Voice voice}) async { {required Map<String, dynamic> answer, required Voice voice}) async {
if (voice == Voice.recorded) { if (voice == Voice.recorded) {
print(answer['recorded_voice_answer']);
await _audioPlayerService.playLocal(answer['recorded_voice_answer']); await _audioPlayerService.playLocal(answer['recorded_voice_answer']);
} else { } else {
await _audioPlayerService.playUrl(answer['sample_voice_answer']); await _audioPlayerService.playUrl(answer['sample_voice_answer']);
@ -214,18 +215,24 @@ class LearnPracticeViewModel extends ReactiveViewModel {
} }
Future<void> nextQuestion( Future<void> nextQuestion(
{required int index, required LearnQuestion question}) async =>
await runBusyFuture(_nextQuestion(index: index, question: question),
busyObject: StateObjects.finishLearnPracticeQuestion);
Future<void> _nextQuestion(
{required int index, required LearnQuestion question}) async { {required int index, required LearnQuestion question}) async {
await stopRecording(); await stopRecording();
_answers.add({ _answers.add({
'busy_object': question.id.toString(), 'busy_object': question.id.toString(),
'sample_text_answer': question.audioCorrectAnswerText, 'sample_text_answer': question.audioCorrectAnswerText,
'sample_voice_answer': question.sampleAnswerVoicePrompt, 'sample_voice_answer': question.sampleAnswerVoicePrompt,
'recorded_voice_answer': _voiceRecorderService.getRecordedAudio(), 'recorded_voice_answer': await _voiceRecorderService.getRecordedAudio(),
}); });
if (index != _questions.length) { if (index != _questions.length) {
_questionSetController.nextPage( _questionSetController.nextPage(
duration: const Duration(milliseconds: 350), curve: Curves.easeInOutCubic,
curve: Curves.easeInOutCubic); duration: const Duration(milliseconds: 350),
);
await playVoicePrompt(_questions[index]); await playVoicePrompt(_questions[index]);
} else { } else {
goTo(3); goTo(3);

View File

@ -5,6 +5,7 @@ import 'package:stacked/stacked.dart';
import 'package:waveform_recorder/waveform_recorder.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/views/learn_practice/learn_practice_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/cancel_learn_practice_sheet.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/custom_linear_progress_indicator.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart'; import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import 'package:yimaru_app/ui/widgets/wave_wrapper.dart'; import 'package:yimaru_app/ui/widgets/wave_wrapper.dart';
@ -70,7 +71,6 @@ class InteractLearnPracticeScreen
children: [ children: [
_buildBodyColumnWrapper(context: context, viewModel: viewModel), _buildBodyColumnWrapper(context: context, viewModel: viewModel),
_buildProgressIndicatorState(viewModel), _buildProgressIndicatorState(viewModel),
_buildPageLoadingIndicatorState(viewModel)
], ],
); );
@ -139,7 +139,9 @@ class InteractLearnPracticeScreen
viewModel.player.state == PlayerState.playing viewModel.player.state == PlayerState.playing
? _buildListeningLabel() ? _buildListeningLabel()
: VoiceRecordingState.recording == viewModel.recordingState : VoiceRecordingState.recording == viewModel.recordingState
? _buildSpeakingLabel() ? viewModel.busy(StateObjects.finishLearnPracticeQuestion)
? const SizedBox(height: 20)
: _buildSpeakingLabel()
: const SizedBox(height: 20); : const SizedBox(height: 20);
Widget _buildListeningLabel() => Text( Widget _buildListeningLabel() => Text(
@ -149,7 +151,7 @@ class InteractLearnPracticeScreen
); );
Widget _buildSpeakingLabel() => Text( Widget _buildSpeakingLabel() => Text(
'You\'re is speaking...', 'You\'re speaking...',
style: style14P400, style: style14P400,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
@ -158,8 +160,11 @@ class InteractLearnPracticeScreen
WaveWrapper(height: 200, child: _buildSpinnerState(viewModel)); WaveWrapper(height: 200, child: _buildSpinnerState(viewModel));
Widget _buildSpinnerState(LearnPracticeViewModel viewModel) => Widget _buildSpinnerState(LearnPracticeViewModel viewModel) =>
viewModel.player.state == PlayerState.playing viewModel.busy(StateObjects.recordLearnPracticeAnswer) ||
? _buildSpinner() viewModel.busy(StateObjects.finishLearnPracticeQuestion)
? Container()
: viewModel.player.state == PlayerState.playing
? _buildSpinner()
: VoiceRecordingState.recording == viewModel.recordingState && : VoiceRecordingState.recording == viewModel.recordingState &&
viewModel.waveController.isRecording viewModel.waveController.isRecording
? _buildSpeakingSpinnerColumn(viewModel) ? _buildSpeakingSpinnerColumn(viewModel)
@ -184,7 +189,6 @@ class InteractLearnPracticeScreen
Widget _buildSpeakingSpinner(LearnPracticeViewModel viewModel) => Widget _buildSpeakingSpinner(LearnPracticeViewModel viewModel) =>
WaveformRecorder( WaveformRecorder(
height: 35, height: 35,
onRecordingStopped: () {},
waveColor: kcPrimaryColor, waveColor: kcPrimaryColor,
durationTextStyle: style14P600, durationTextStyle: style14P600,
controller: viewModel.waveController, controller: viewModel.waveController,
@ -221,7 +225,7 @@ class InteractLearnPracticeScreen
[ [
_buildActionLabel(), _buildActionLabel(),
verticalSpaceMedium, verticalSpaceMedium,
_buildButtonsRowWrapper(context: context, viewModel: viewModel), _buildButtonRowContainer(context: context, viewModel: viewModel),
verticalSpaceMedium, verticalSpaceMedium,
]; ];
@ -231,6 +235,27 @@ class InteractLearnPracticeScreen
textAlign: TextAlign.center, 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( Widget _buildButtonsRowWrapper(
{required BuildContext context, {required BuildContext context,
required LearnPracticeViewModel viewModel}) => required LearnPracticeViewModel viewModel}) =>
@ -268,7 +293,12 @@ class InteractLearnPracticeScreen
); );
Widget _buildMicButtonWrapper(LearnPracticeViewModel viewModel) => 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( Widget _buildMicButton(LearnPracticeViewModel viewModel) => ElevatedButton(
style: ButtonStyle( style: ButtonStyle(
@ -335,7 +365,11 @@ class InteractLearnPracticeScreen
Widget _buildProgressIndicatorState(LearnPracticeViewModel viewModel) => Widget _buildProgressIndicatorState(LearnPracticeViewModel viewModel) =>
viewModel.recordingState == VoiceRecordingState.pending viewModel.recordingState == VoiceRecordingState.pending
? _buildProgressIndicatorWrapper(viewModel) ? viewModel.busy(StateObjects.finishLearnPracticeQuestion) ||
viewModel.busy(StateObjects.learnPracticeQuestion) ||
viewModel.busy(StateObjects.recordLearnPracticeAnswer)
? Container()
: _buildProgressIndicatorWrapper(viewModel)
: Container(); : Container();
Widget _buildProgressIndicatorWrapper(LearnPracticeViewModel viewModel) => Widget _buildProgressIndicatorWrapper(LearnPracticeViewModel viewModel) =>
@ -349,17 +383,12 @@ class InteractLearnPracticeScreen
Widget _buildProgressIndicatorSpacer(LearnPracticeViewModel viewModel) => Widget _buildProgressIndicatorSpacer(LearnPracticeViewModel viewModel) =>
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildProgressIndicator(viewModel), child: _buildLinearProgressIndicator(viewModel),
); );
Widget _buildProgressIndicator(LearnPracticeViewModel viewModel) => Widget _buildLinearProgressIndicator(LearnPracticeViewModel viewModel) =>
CustomLinearProgressIndicator( CustomLinearProgressIndicator(
activeColor: kcPrimaryColor, activeColor: kcPrimaryColor,
progress: viewModel.progress, progress: viewModel.progress,
backgroundColor: kcVeryLightGrey); backgroundColor: kcVeryLightGrey);
Widget _buildPageLoadingIndicatorState(LearnPracticeViewModel viewModel) =>
viewModel.busy(StateObjects.learnPracticeQuestion)
? const PageLoadingIndicator()
: Container();
} }

View File

@ -46,7 +46,5 @@ class LearnPracticeResultsWrapper
itemBuilder: (context, index) => _buildResult(viewModel.answers[index]), itemBuilder: (context, index) => _buildResult(viewModel.answers[index]),
); );
Widget _buildResult(Map<String, dynamic> answer) => LearnPracticeResultCard( Widget _buildResult(Map<String, dynamic> answer) => LearnPracticeResultCard(answer: answer);
answer: answer,
);
} }

View File

@ -1,5 +1,5 @@
name: yimaru_app name: yimaru_app
version: 0.1.9+11 version: 0.1.10+12
publish_to: 'none' publish_to: 'none'
description: A new Flutter project. description: A new Flutter project.