Hide voice waveform visualization
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m39s

This commit is contained in:
Ruslan Bakiev
2026-05-13 17:59:30 +07:00
parent 04fa49737d
commit 5b2cd4158c

View File

@@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:latlong2/latlong.dart' hide Path;
import 'package:waveform_flutter/waveform_flutter.dart';
import 'package:waveform_flutter/waveform_flutter.dart' show Amplitude;
import 'package:waveform_recorder/waveform_recorder.dart';
import '../api/mapflow_api.dart';
@@ -870,7 +870,6 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
isRecording: _recording,
isSubmitting: _submitting,
micAllowed: _micAllowed,
waveController: _waveController,
liveLevel: _liveLevel,
canContinue: widget.hasTelegramAuth && informationProgress >= 1,
onToggleRecording: _toggleRecording,
@@ -1069,7 +1068,6 @@ class _VoiceStep extends StatelessWidget {
required this.isRecording,
required this.isSubmitting,
required this.micAllowed,
required this.waveController,
required this.liveLevel,
required this.canContinue,
required this.onToggleRecording,
@@ -1082,7 +1080,6 @@ class _VoiceStep extends StatelessWidget {
final bool isRecording;
final bool isSubmitting;
final bool micAllowed;
final WaveformRecorderController waveController;
final double liveLevel;
final bool canContinue;
final Future<void> Function() onToggleRecording;
@@ -1106,23 +1103,7 @@ class _VoiceStep extends StatelessWidget {
),
const SizedBox(height: 10),
],
Expanded(
flex: 5,
child: _VoiceProgressGrid(progress: informationProgress),
),
const SizedBox(height: 10),
Expanded(
flex: 3,
child: SizedBox(
width: double.infinity,
child: _LibraryWaveSurface(
controller: waveController,
active: isRecording,
progress: informationProgress,
liveLevel: liveLevel,
),
),
),
Expanded(child: _VoiceProgressGrid(progress: informationProgress)),
if (!micAllowed)
const Padding(
padding: EdgeInsets.only(bottom: 10),
@@ -1152,20 +1133,20 @@ class _VoiceProgressGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
const columns = 20;
const rows = 10;
const columns = 24;
const rows = 14;
const total = columns * rows;
final filled = (progress.clamp(0.0, 1.0) * total).round();
return LayoutBuilder(
builder: (context, constraints) {
const gap = 5.0;
const gap = 4.0;
final maxCellWidth =
(constraints.maxWidth - gap * (columns - 1)) / columns;
final maxCellHeight = (constraints.maxHeight - gap * (rows - 1)) / rows;
final cellSize = math.max(
6.0,
math.min(maxCellWidth, maxCellHeight).clamp(6.0, 18.0),
math.min(maxCellWidth, maxCellHeight).clamp(5.0, 16.0),
);
final gridWidth = columns * cellSize + gap * (columns - 1);
final gridHeight = rows * cellSize + gap * (rows - 1);
@@ -1180,7 +1161,7 @@ class _VoiceProgressGrid extends StatelessWidget {
children: [
for (var index = 0; index < total; index++)
_VoiceProgressCell(
filled: _gridOrder(index, total) < filled,
filled: _gridOrder(index, columns, rows) < filled,
size: cellSize,
),
],
@@ -1191,7 +1172,20 @@ class _VoiceProgressGrid extends StatelessWidget {
);
}
static int _gridOrder(int index, int total) => ((index + 7) * 29) % total;
static int _gridOrder(int index, int columns, int rows) {
final row = index ~/ columns;
final column = index % columns;
final centerX = (columns - 1) / 2;
final centerY = (rows - 1) / 2;
final dx = column - centerX;
final dy = row - centerY;
final radius = math.sqrt(dx * dx + dy * dy);
final angle = math.atan2(dy, dx);
final shell = (radius * 7.0 + angle * 5.0).floor();
final noise = (math.sin((column + 1) * 37.17 + (row + 1) * 91.43) * 10000)
.abs();
return ((shell * 31 + noise.floor()) % (columns * rows));
}
}
class _VoiceProgressCell extends StatelessWidget {
@@ -1363,165 +1357,6 @@ class _VoiceRecordButtonState extends State<_VoiceRecordButton>
}
}
class _LibraryWaveSurface extends StatelessWidget {
const _LibraryWaveSurface({
required this.controller,
required this.active,
required this.progress,
required this.liveLevel,
});
final WaveformRecorderController controller;
final bool active;
final double progress;
final double liveLevel;
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: const Color(
0xFFFF2D75,
).withValues(alpha: active ? 0.16 + liveLevel * 0.16 : 0.04),
blurRadius: 96,
spreadRadius: 10,
),
BoxShadow(
color: const Color(
0xFF38F5D3,
).withValues(alpha: active ? 0.08 + liveLevel * 0.10 : 0.03),
blurRadius: 120,
spreadRadius: 8,
),
],
),
),
),
Positioned.fill(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 0),
child: active
? AnimatedWaveList(
key: ValueKey(controller.startTime),
stream: controller.amplitudeStream,
barBuilder: (animation, amplitude) => _VoiceWaveBar(
animation: animation,
amplitude: amplitude,
progress: progress,
),
)
: _IdleWaveBars(progress: progress),
),
),
],
);
}
}
class _VoiceWaveBar extends StatelessWidget {
const _VoiceWaveBar({
required this.animation,
required this.amplitude,
required this.progress,
});
final Animation<double> animation;
final Amplitude amplitude;
final double progress;
@override
Widget build(BuildContext context) {
final level = _amplitudeLevel(amplitude.current);
final height = 18 + level * 180;
final color = Color.lerp(
Colors.white.withValues(alpha: 0.28),
const Color(0xFFFF2D75),
progress.clamp(0.0, 1.0),
)!;
return SizeTransition(
sizeFactor: animation,
axis: Axis.horizontal,
child: Align(
alignment: Alignment.center,
child: Container(
width: 6,
height: height,
margin: const EdgeInsets.symmetric(horizontal: 3),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
color.withValues(alpha: 0.42),
color,
const Color(0xFFFF7A90),
],
),
boxShadow: [
BoxShadow(
color: color.withValues(alpha: 0.38),
blurRadius: 18,
spreadRadius: 1,
),
],
),
),
),
);
}
double _amplitudeLevel(double db) {
final normalized = ((db.clamp(-76.0, -6.0) + 76.0) / 70.0).clamp(0.0, 1.0);
return math.pow(normalized, 0.62).toDouble();
}
}
class _IdleWaveBars extends StatelessWidget {
const _IdleWaveBars({required this.progress});
final double progress;
@override
Widget build(BuildContext context) {
final color = Color.lerp(
Colors.white.withValues(alpha: 0.18),
const Color(0xFFFF2D75),
progress.clamp(0.0, 1.0),
)!;
return Center(
child: SizedBox(
height: 210,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: List.generate(26, (index) {
final distance = (index - 12.5).abs();
final height = 22 + math.max(0.0, 1 - distance / 13) * 94;
return Container(
width: 6,
height: height,
margin: const EdgeInsets.symmetric(horizontal: 3),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(20),
),
);
}),
),
),
);
}
}
class _StepLayout extends StatelessWidget {
const _StepLayout({required this.body, this.action});