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