Improve voice recording screen
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 1m46s

This commit is contained in:
Ruslan Bakiev
2026-05-09 15:10:18 +07:00
parent c02c050607
commit 02b3521320

View File

@@ -1034,74 +1034,147 @@ class _VoiceStep extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _StepLayout(
body: Column(
children: [
const Spacer(),
Text(
placeName.trim().isEmpty ? 'Место' : placeName.trim(),
textAlign: TextAlign.center,
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w900),
),
const SizedBox(height: 8),
Text(hasTelegramAuth ? 'Минимум $minimumSeconds секунд' : ''),
const SizedBox(height: 26),
_VoiceWave(samples: samples, active: isRecording),
const SizedBox(height: 24),
SizedBox(
width: 132,
height: 132,
child: FilledButton(
onPressed: isSubmitting || !hasTelegramAuth
? null
: () => onToggleRecording(),
style: FilledButton.styleFrom(shape: const CircleBorder()),
child: Icon(isRecording ? Icons.stop : Icons.mic, size: 54),
final progress = (seconds / minimumSeconds).clamp(0.0, 1.0);
final showNext = canContinue && !isRecording;
return Column(
children: [
Text(
placeName.trim().isEmpty ? 'Место' : placeName.trim(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w900),
),
const SizedBox(height: 10),
Expanded(
child: Center(
child: _VoiceWave(
samples: samples,
active: isRecording,
progress: progress,
),
),
const SizedBox(height: 22),
Text(
time,
style: Theme.of(
context,
).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.w900),
),
if (!micAllowed)
const Padding(
padding: EdgeInsets.only(bottom: 10),
child: Icon(Icons.mic_off_outlined, size: 22),
),
const SizedBox(height: 12),
LinearProgressIndicator(
value: (seconds / minimumSeconds).clamp(0.0, 1.0),
AnimatedSwitcher(
duration: const Duration(milliseconds: 180),
child: showNext
? Padding(
key: const ValueKey('next'),
padding: const EdgeInsets.only(bottom: 14),
child: FilledButton(
onPressed: isSubmitting ? null : onNext,
child: Text(isSubmitting ? 'Отправляем' : 'Далее'),
),
)
: const SizedBox.shrink(key: ValueKey('empty-next')),
),
_VoiceRecordButton(
time: time,
progress: progress,
isRecording: isRecording,
enabled: hasTelegramAuth && !isSubmitting,
onPressed: onToggleRecording,
),
],
);
}
}
class _VoiceRecordButton extends StatelessWidget {
const _VoiceRecordButton({
required this.time,
required this.progress,
required this.isRecording,
required this.enabled,
required this.onPressed,
});
final String time;
final double progress;
final bool isRecording;
final bool enabled;
final Future<void> Function() onPressed;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return SizedBox(
width: 148,
height: 148,
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 148,
height: 148,
child: CircularProgressIndicator(
value: progress,
strokeWidth: 8,
strokeCap: StrokeCap.round,
backgroundColor: colorScheme.primary.withValues(alpha: 0.14),
),
),
SizedBox(
width: 118,
height: 118,
child: FilledButton(
onPressed: enabled ? () => onPressed() : null,
style: FilledButton.styleFrom(
shape: const CircleBorder(),
padding: EdgeInsets.zero,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(isRecording ? Icons.stop : Icons.mic, size: 42),
const SizedBox(height: 6),
Text(
time,
style: Theme.of(context).textTheme.labelLarge?.copyWith(
color: colorScheme.onPrimary,
fontWeight: FontWeight.w900,
),
),
],
),
),
),
if (!micAllowed) ...[
const SizedBox(height: 12),
const Icon(Icons.mic_off_outlined, size: 22),
],
const Spacer(),
],
),
action: FilledButton(
onPressed: canContinue && !isSubmitting ? onNext : null,
child: Text(isSubmitting ? 'Отправляем' : 'Далее'),
),
);
}
}
class _VoiceWave extends StatelessWidget {
const _VoiceWave({required this.samples, required this.active});
const _VoiceWave({
required this.samples,
required this.active,
required this.progress,
});
final List<double> samples;
final bool active;
final double progress;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 112,
height: double.infinity,
width: double.infinity,
child: CustomPaint(
painter: _VoiceWavePainter(
samples: samples,
active: active,
progress: progress,
color: Theme.of(context).colorScheme.primary,
),
),
@@ -1113,16 +1186,19 @@ class _VoiceWavePainter extends CustomPainter {
const _VoiceWavePainter({
required this.samples,
required this.active,
required this.progress,
required this.color,
});
final List<double> samples;
final bool active;
final double progress;
final Color color;
@override
void paint(Canvas canvas, Size size) {
final centerY = size.height / 2;
final quietScale = active ? 1.0 : 0.42 + progress * 0.32;
final top = <Offset>[];
final bottom = <Offset>[];
@@ -1131,7 +1207,8 @@ class _VoiceWavePainter extends CustomPainter {
? 0.0
: index / (samples.length - 1) * size.width;
final envelope = math.sin(index / (samples.length - 1) * math.pi);
final amplitude = samples[index] * envelope * size.height * 0.46;
final amplitude =
samples[index] * quietScale * envelope * size.height * 0.46;
final yTop = centerY - amplitude;
final yBottom = centerY + amplitude;