From 0532f6aa88cb6927f043d2aea4f5010cd291f1da Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Thu, 14 May 2026 16:35:28 +0700 Subject: [PATCH] Restore waveform recording controller --- lib/screens/mapflow_shell.dart | 82 +++++++++------------------------- pubspec.lock | 20 ++++++++- pubspec.yaml | 4 +- 3 files changed, 42 insertions(+), 64 deletions(-) diff --git a/lib/screens/mapflow_shell.dart b/lib/screens/mapflow_shell.dart index 2c542c1..507fe37 100644 --- a/lib/screens/mapflow_shell.dart +++ b/lib/screens/mapflow_shell.dart @@ -2,13 +2,13 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math' as math; -import 'package:cross_file/cross_file.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:latlong2/latlong.dart' hide Path; -import 'package:record/record.dart' as record; +import 'package:waveform_flutter/waveform_flutter.dart' show Amplitude; +import 'package:waveform_recorder/waveform_recorder.dart'; import '../api/mapflow_api.dart'; import '../auth/telegram_login_button.dart'; @@ -711,30 +711,26 @@ class AddExperienceFlow extends ConsumerStatefulWidget { class _AddExperienceFlowState extends ConsumerState { static const _minimumInformationUnits = 16.0; static const _nearbyPlaceRadiusMeters = 200; - static const _amplitudeInterval = Duration(milliseconds: 140); - static const _recordConfig = record.RecordConfig( - encoder: record.AudioEncoder.wav, - numChannels: 1, - sampleRate: 44100, - autoGain: true, - echoCancel: true, - noiseSuppress: true, - ); final _api = MapflowApi(); - final _audioRecorder = record.AudioRecorder(); - final _recordStopwatch = Stopwatch(); + final _waveController = WaveformRecorderController( + interval: const Duration(milliseconds: 45), + config: const RecordConfig( + numChannels: 1, + sampleRate: 44100, + autoGain: true, + echoCancel: true, + noiseSuppress: true, + ), + ); Future>? _nearbyPlacesFuture; - StreamSubscription? _amplitudeSub; - Timer? _progressTimer; - XFile? _recordedFile; + StreamSubscription? _amplitudeSub; var _step = 0; var _informationUnits = 0.0; var _recording = false; var _submitting = false; var _micAllowed = true; - var _heardVoice = false; var _noiseDb = -72.0; var _voicePeakDb = -34.0; var _liveLevel = 0.0; @@ -748,8 +744,7 @@ class _AddExperienceFlowState extends ConsumerState { @override void dispose() { _amplitudeSub?.cancel(); - _progressTimer?.cancel(); - _audioRecorder.dispose(); + _waveController.dispose(); super.dispose(); } @@ -775,27 +770,14 @@ class _AddExperienceFlowState extends ConsumerState { } Future _startRecording() async { - _recordedFile = null; - final path = 'mapflow-${DateTime.now().microsecondsSinceEpoch}.wav'; - await _audioRecorder.start(_recordConfig, path: path); + await _waveController.startRecording(); await _amplitudeSub?.cancel(); - _recordStopwatch - ..reset() - ..start(); _lastInformationAt = DateTime.now(); - _amplitudeSub = _audioRecorder - .onAmplitudeChanged(_amplitudeInterval) - .listen(_handleAmplitude); - _progressTimer?.cancel(); - _progressTimer = Timer.periodic( - _amplitudeInterval, - (_) => _tickRecordingProgress(), - ); + _amplitudeSub = _waveController.amplitudeStream.listen(_handleAmplitude); setState(() { _micAllowed = true; _recording = true; - _heardVoice = false; _liveLevel = 0; _informationUnits = 0; }); @@ -804,13 +786,7 @@ class _AddExperienceFlowState extends ConsumerState { Future _stopRecording() async { await _amplitudeSub?.cancel(); _amplitudeSub = null; - _progressTimer?.cancel(); - _progressTimer = null; - final path = await _audioRecorder.stop(); - _recordStopwatch.stop(); - if (path != null && path.isNotEmpty) { - _recordedFile = XFile(path, mimeType: 'audio/wav'); - } + await _waveController.stopRecording(); _lastInformationAt = null; if (!mounted) { return; @@ -821,28 +797,14 @@ class _AddExperienceFlowState extends ConsumerState { }); } - void _handleAmplitude(record.Amplitude amplitude) { + void _handleAmplitude(Amplitude amplitude) { final currentDb = amplitude.current; + final now = DateTime.now(); final level = _normalizeDbLevel(currentDb); + final informationDelta = _consumeInformationDelta(level, now); setState(() { _liveLevel = _smoothLevel(_liveLevel, level); - _heardVoice = _heardVoice || level > 0.08; - }); - } - - void _tickRecordingProgress() { - if (!_recording) { - return; - } - - final effectiveLevel = _heardVoice ? math.max(_liveLevel, 0.34) : _liveLevel; - final informationDelta = _consumeInformationDelta( - effectiveLevel, - DateTime.now(), - ); - - setState(() { _informationUnits = math.min( _minimumInformationUnits, _informationUnits + informationDelta, @@ -850,7 +812,7 @@ class _AddExperienceFlowState extends ConsumerState { }); ref .read(placeControllerProvider.notifier) - .setReviewDuration(_recordStopwatch.elapsed); + .setReviewDuration(_waveController.timeElapsed); } double _normalizeDbLevel(double currentDb) { @@ -918,7 +880,7 @@ class _AddExperienceFlowState extends ConsumerState { onSelect: (place) async { setState(() => _submitting = true); controller.setReviewPlace(place.name); - final file = _recordedFile; + final file = _waveController.file; if (file == null) { throw StateError('Voice recording file is required.'); } diff --git a/pubspec.lock b/pubspec.lock index 9832987..05c1797 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -106,7 +106,7 @@ packages: source: hosted version: "1.15.0" cross_file: - dependency: "direct main" + dependency: transitive description: name: cross_file sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" @@ -641,7 +641,7 @@ packages: source: hosted version: "2.2.0" record: - dependency: "direct main" + dependency: transitive description: name: record sha256: d5b6b334f3ab02460db6544e08583c942dbf23e3504bf1e14fd4cbe3d9409277 @@ -925,6 +925,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + waveform_flutter: + dependency: "direct main" + description: + name: waveform_flutter + sha256: "08c9e98d4cf119428d8b3c083ed42c11c468623eaffdf30420ae38e36662922a" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + waveform_recorder: + dependency: "direct main" + description: + name: waveform_recorder + sha256: "1ca0a19b143d1bdef2adfb3d28f0627c18aee5285235c8cf81a89bf29a0420e1" + url: "https://pub.dev" + source: hosted + version: "1.8.0" web: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 8efd4eb..01eef5e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,9 +40,9 @@ dependencies: http: ^1.6.0 web: ^1.1.1 geolocator: ^14.0.2 - record: ^6.2.0 flutter_svg: ^2.3.0 - cross_file: ^0.3.5+2 + waveform_recorder: ^1.8.0 + waveform_flutter: ^1.2.0 dev_dependencies: flutter_test: