diff --git a/lib/screens/mapflow_shell.dart b/lib/screens/mapflow_shell.dart index 6bdb7bf..718de8b 100644 --- a/lib/screens/mapflow_shell.dart +++ b/lib/screens/mapflow_shell.dart @@ -737,6 +737,7 @@ class _AddExperienceFlowState extends ConsumerState { Future>? _nearbyPlacesFuture; StreamSubscription? _amplitudeSub; + Timer? _visualTimer; var _step = 0; var _informationUnits = 0.0; var _recording = false; @@ -745,7 +746,9 @@ class _AddExperienceFlowState extends ConsumerState { var _noiseDb = -72.0; var _voicePeakDb = -34.0; var _liveLevel = 0.0; + var _visualPhase = 0.0; DateTime? _lastInformationAt; + DateTime? _lastAmplitudeAt; @override void initState() { @@ -755,6 +758,7 @@ class _AddExperienceFlowState extends ConsumerState { @override void dispose() { _amplitudeSub?.cancel(); + _visualTimer?.cancel(); _waveController.dispose(); super.dispose(); } @@ -784,7 +788,9 @@ class _AddExperienceFlowState extends ConsumerState { await _waveController.startRecording(); await _amplitudeSub?.cancel(); _lastInformationAt = DateTime.now(); + _lastAmplitudeAt = null; _amplitudeSub = _waveController.amplitudeStream.listen(_handleAmplitude); + _startVisualTick(); setState(() { _micAllowed = true; @@ -798,8 +804,11 @@ class _AddExperienceFlowState extends ConsumerState { Future _stopRecording() async { await _amplitudeSub?.cancel(); _amplitudeSub = null; + _visualTimer?.cancel(); + _visualTimer = null; await _waveController.stopRecording(); _lastInformationAt = null; + _lastAmplitudeAt = null; if (!mounted) { return; } @@ -813,12 +822,16 @@ class _AddExperienceFlowState extends ConsumerState { final currentDb = amplitude.current; final now = DateTime.now(); final level = _normalizeDbLevel(currentDb); - final informationDelta = _consumeInformationDelta(level, now); + final informationLevel = currentDb > -64 + ? math.max(0.22, level) + : level * 0.35; + final informationDelta = _consumeInformationDelta(informationLevel, now); + _lastAmplitudeAt = now; setState(() { _voiceSamples ..removeAt(0) - ..add(level); + ..add(math.max(0.04, level)); _liveLevel = _smoothLevel(_liveLevel, level); _informationUnits = math.min( _minimumInformationUnits, @@ -830,6 +843,30 @@ class _AddExperienceFlowState extends ConsumerState { .setReviewDuration(_waveController.timeElapsed); } + void _startVisualTick() { + _visualTimer?.cancel(); + _visualTimer = Timer.periodic(const Duration(milliseconds: 70), (_) { + if (!mounted || !_recording) { + return; + } + + _visualPhase += 0.34; + final hasFreshAmplitude = + _lastAmplitudeAt != null && + DateTime.now().difference(_lastAmplitudeAt!).inMilliseconds < 160; + final baseLevel = hasFreshAmplitude ? _liveLevel : 0.10; + final wave = + (math.sin(_visualPhase) * 0.5 + 0.5) * (0.16 + baseLevel * 0.34); + final visualLevel = (0.06 + baseLevel * 0.70 + wave).clamp(0.0, 1.0); + + setState(() { + _voiceSamples + ..removeAt(0) + ..add(visualLevel); + }); + }); + } + double _normalizeDbLevel(double currentDb) { final db = currentDb.clamp(-160.0, 0.0); if (db < _noiseDb) { @@ -1095,7 +1132,7 @@ class _VoiceStep extends StatelessWidget { @override Widget build(BuildContext context) { - final canFinish = canContinue && !isRecording; + final canFinish = canContinue; return Column( children: [ @@ -1392,7 +1429,8 @@ class _VoiceInformationPainter extends CustomPainter { final signal = samples.isEmpty ? 0.0 : samples[sampleIndex].clamp(0.0, 1.0); - final filled = _cellOrder(cellIndex, totalCells) < activeCells; + final fillOrder = (rows - 1 - row) * columns + column; + final filled = fillOrder < activeCells; final x = startX + column * (cellSize + gap); final y = startY + row * (cellSize + gap); final rect = RRect.fromRectAndRadius( @@ -1442,8 +1480,6 @@ class _VoiceInformationPainter extends CustomPainter { } } - int _cellOrder(int index, int total) => ((index + 11) * 73) % total; - double _hashUnit(int index) { final value = math.sin(index * 12.9898 + 78.233) * 43758.5453; return value - value.floorToDouble();