fix(learn): Fix user recorded voice playing issue

This commit is contained in:
BisratHailu 2026-05-01 13:47:21 +03:00
parent c2fc40fc3b
commit 539d8bf6c2
9 changed files with 61 additions and 37 deletions

View File

@ -506,10 +506,11 @@ class ApiService {
final Response response = await _service.dio final Response response = await _service.dio
.get('$kBaseUrl/api/$kApiVersionUrl/$kModulesUrl/$id/$kPracticesUrl'); .get('$kBaseUrl/api/$kApiVersionUrl/$kModulesUrl/$id/$kPracticesUrl');
print('MODULE PRACTICES: ${response.data}');
if (response.statusCode == 200) { if (response.statusCode == 200) {
var data = response.data; var data = response.data;
var decodedData = data['data'] as List; var decodedData = data['data']['practices'] as List;
practices = decodedData.map( practices = decodedData.map(
(e) { (e) {
return LearnPractice.fromJson(e); return LearnPractice.fromJson(e);

View File

@ -1,3 +1,6 @@
import 'dart:async';
import 'package:async/async.dart';
import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers/audioplayers.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
@ -9,13 +12,19 @@ class AudioPlayerService with ListenableServiceMixin {
AudioPlayer get player => _player; AudioPlayer get player => _player;
final _durationController = StreamController<Duration>.broadcast();
AudioPlayerService() { AudioPlayerService() {
_player.setReleaseMode(ReleaseMode.stop); _player.setReleaseMode(ReleaseMode.stop);
} }
// Streams // Streams
Stream<Duration> get durationStream => StreamGroup.merge([
_player.onDurationChanged,
_durationController.stream,
]);
Stream<Duration> get positionStream => _player.onPositionChanged; Stream<Duration> get positionStream => _player.onPositionChanged;
Stream<Duration> get durationStream => _player.onDurationChanged;
// Optional: player state // Optional: player state
Stream<PlayerState> get stateStream => _player.onPlayerStateChanged; Stream<PlayerState> get stateStream => _player.onPlayerStateChanged;
@ -28,10 +37,16 @@ class AudioPlayerService with ListenableServiceMixin {
} }
await _player.play(UrlSource(playableUrl)); await _player.play(UrlSource(playableUrl));
// 👇 Force duration fetch
final dur = await _player.getDuration();
if (dur != null) {
_durationController.add(dur);
}
} }
Future<void> playLocal(String url) async { Future<void> playLocal(String url) async {
await _player.play(UrlSource(url)); await _player.play(DeviceFileSource(url));
} }
Future<void> pause() async => await _player.pause(); Future<void> pause() async => await _player.pause();

View File

@ -148,6 +148,8 @@ class AuthenticationService with ListenableServiceMixin {
// Get user data // Get user data
Future<User?> getUser() async { Future<User?> getUser() async {
print('GENDER:');
print(await _secureService.getString('gender'));
_user = User( _user = User(
userId: await _secureService.getInt('userId'), userId: await _secureService.getInt('userId'),
email: await _secureService.getString('email'), email: await _secureService.getString('email'),

View File

@ -1,7 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.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';
@ -39,25 +37,19 @@ class VoiceRecorderService with ListenableServiceMixin {
// Get recorded audio // Get recorded audio
Future<String?> getRecordedAudio() async { Future<String?> getRecordedAudio() async {
final file = _waveController.file; final recorded = _waveController.file;
print('RECORDED $file'); if (recorded == null) return null;
if (file == null) return null;
await _saveRecordedAudio(file); final generator = Random();
// return file.path; int random = generator.nextInt(100);
String? voice = await _saveRecordedAudio(file);
return voice;
}
Future<String?> _saveRecordedAudio(XFile? file) async { final dir = await getTemporaryDirectory();
late File voice;
final voiceName = basename(file?.name ?? '');
final Directory appDir = await getApplicationDocumentsDirectory();
final localImagePath = join(appDir.path, voiceName); final playable = File('${dir.path}/temp_audio_$random.aac');
voice = File(localImagePath);
//voice.writeAsBytes(await file?.); await playable.writeAsBytes(await recorded.readAsBytes(), flush: true);
return voice.path;
return playable.path;
} }
} }

View File

@ -59,10 +59,12 @@ class LearnPracticeViewModel extends ReactiveViewModel {
Duration get duration => _duration; Duration get duration => _duration;
double get progress { double get progress {
print('DURATION: ${_duration.inMilliseconds}');
if (_duration.inMilliseconds == 0) return 0; if (_duration.inMilliseconds == 0) return 0;
return _position.inMilliseconds / _duration.inMilliseconds; return _position.inMilliseconds / _duration.inMilliseconds;
} }
// Voice recorder // Voice recorder
WaveformRecorderController get _waveController => WaveformRecorderController get _waveController =>
_voiceRecorderService.waveController; _voiceRecorderService.waveController;
@ -128,14 +130,14 @@ class LearnPracticeViewModel extends ReactiveViewModel {
// Play practice audio // Play practice audio
void _listenToAudio() { void _listenToAudio() {
_audioPlayerService.durationStream.listen((dur) { _audioPlayerService.durationStream.listen((dur) {
if (dur.inMilliseconds > 0) {
_duration = dur; _duration = dur;
print('DURATION: $_duration');
rebuildUi(); rebuildUi();
}
}); });
_audioPlayerService.positionStream.listen((pos) { _audioPlayerService.positionStream.listen((pos) {
_position = pos; _position = pos;
print('POSITION: $_position');
rebuildUi(); rebuildUi();
}); });
} }
@ -255,13 +257,14 @@ class LearnPracticeViewModel extends ReactiveViewModel {
if (await _statusChecker.checkConnection()) { if (await _statusChecker.checkConnection()) {
if (practice == LearnPractices.course) { if (practice == LearnPractices.course) {
_practices = await _apiService.getLearnCoursePractices(id); _practices = await _apiService.getLearnCoursePractices(id);
await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0); await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0);
} else if (practice == LearnPractices.module) { } else if (practice == LearnPractices.module) {
_practices = await _apiService.getLearnModulePractices(id); _practices = await _apiService.getLearnModulePractices(id);
await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0); await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0);
} else { } else {
_practices = await _apiService.getLearnLessonPractices(id); _practices = await _apiService.getLearnLessonPractices(id);
print('PRACTICE LENGTH: ${_practices.length}');
await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0); await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0);
} }
} }

View File

@ -370,7 +370,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
Widget _buildMaleRadioTile(ProfileDetailViewModel viewModel) => Widget _buildMaleRadioTile(ProfileDetailViewModel viewModel) =>
RadioListTile<String?>( RadioListTile<String?>(
value: 'Male', value: 'male',
title: _buildMaleTitle(), title: _buildMaleTitle(),
activeColor: kcPrimaryColor, activeColor: kcPrimaryColor,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
@ -400,7 +400,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
Widget _buildFemaleRadioTile(ProfileDetailViewModel viewModel) => Widget _buildFemaleRadioTile(ProfileDetailViewModel viewModel) =>
RadioListTile<String?>( RadioListTile<String?>(
value: 'Female', value: 'female',
title: _buildFemaleTitle(), title: _buildFemaleTitle(),
activeColor: kcPrimaryColor, activeColor: kcPrimaryColor,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,

View File

@ -1,6 +1,7 @@
import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/widgets/custom_circular_progress_indicator.dart';
import '../common/app_colors.dart'; import '../common/app_colors.dart';
import '../common/enmus.dart'; import '../common/enmus.dart';
@ -50,13 +51,22 @@ class LearnPracticeAnswerCard extends ViewModelWidget<LearnPracticeViewModel> {
); );
Widget _buildButtonState(LearnPracticeViewModel viewModel) => Widget _buildButtonState(LearnPracticeViewModel viewModel) =>
(viewModel.busy(answer['busy_object']) && viewModel.playing == voice) || viewModel.busyObject == answer['busy_object'] &&
(viewModel.busyObject == answer['busy_object'] && viewModel.playing == voice
viewModel.playing == voice && ? viewModel.busy(answer['busy_object'])
viewModel.player.state == PlayerState.playing) ? _buildProgressIndicatorWrapper()
: viewModel.player.state == PlayerState.playing
? _buildPauseIcon() ? _buildPauseIcon()
: _buildPlayIcon()
: _buildPlayIcon(); : _buildPlayIcon();
Widget _buildProgressIndicatorWrapper() => Center(
child: _buildProgressIndicator(),
);
Widget _buildProgressIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite);
Widget _buildPlayIcon() => const Icon( Widget _buildPlayIcon() => const Icon(
Icons.play_arrow_rounded, Icons.play_arrow_rounded,
size: 25, size: 25,

View File

@ -50,13 +50,13 @@ packages:
source: hosted source: hosted
version: "2.7.0" version: "2.7.0"
async: async:
dependency: transitive dependency: "direct main"
description: description:
name: async name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.13.0" version: "2.13.1"
audioplayers: audioplayers:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -12,6 +12,7 @@ dependencies:
intl: any intl: any
dio: ^5.9.0 dio: ^5.9.0
path: ^1.9.1 path: ^1.9.1
async: ^2.13.1
pinput: ^6.0.1 pinput: ^6.0.1
stacked: ^3.4.0 stacked: ^3.4.0
iconsax: ^0.0.8 iconsax: ^0.0.8