From 04fa49737dfc586850b5ccf7f20da48bca2f8af6 Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Wed, 13 May 2026 17:50:17 +0700 Subject: [PATCH] Expand voice recording layout --- lib/screens/mapflow_shell.dart | 186 +++++++++++++++++++-------------- 1 file changed, 110 insertions(+), 76 deletions(-) diff --git a/lib/screens/mapflow_shell.dart b/lib/screens/mapflow_shell.dart index 39022cb..3c96c6b 100644 --- a/lib/screens/mapflow_shell.dart +++ b/lib/screens/mapflow_shell.dart @@ -1106,11 +1106,15 @@ class _VoiceStep extends StatelessWidget { ), const SizedBox(height: 10), ], - _VoiceProgressGrid(progress: informationProgress), - const SizedBox(height: 18), Expanded( - child: Align( - alignment: Alignment.bottomCenter, + flex: 5, + child: _VoiceProgressGrid(progress: informationProgress), + ), + const SizedBox(height: 10), + Expanded( + flex: 3, + child: SizedBox( + width: double.infinity, child: _LibraryWaveSurface( controller: waveController, active: isRecording, @@ -1148,50 +1152,80 @@ class _VoiceProgressGrid extends StatelessWidget { @override Widget build(BuildContext context) { - const columns = 14; - const rows = 3; + const columns = 20; + const rows = 10; const total = columns * rows; final filled = (progress.clamp(0.0, 1.0) * total).round(); - return SizedBox( - width: 230, - child: Wrap( - spacing: 5, - runSpacing: 5, - alignment: WrapAlignment.center, - children: [ - for (var index = 0; index < total; index++) - AnimatedContainer( - duration: const Duration(milliseconds: 180), - curve: Curves.easeOutCubic, - width: 10, - height: 10, - decoration: BoxDecoration( - color: _gridOrder(index, total) < filled - ? const Color(0xFFFF2D75) - : Colors.white.withValues(alpha: 0.11), - borderRadius: BorderRadius.circular(3), - boxShadow: _gridOrder(index, total) < filled - ? [ - BoxShadow( - color: const Color( - 0xFFFF2D75, - ).withValues(alpha: 0.34), - blurRadius: 12, - spreadRadius: 1, - ), - ] - : null, - ), + return LayoutBuilder( + builder: (context, constraints) { + const gap = 5.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), + ); + final gridWidth = columns * cellSize + gap * (columns - 1); + final gridHeight = rows * cellSize + gap * (rows - 1); + + return Center( + child: SizedBox( + width: gridWidth, + height: gridHeight, + child: Wrap( + spacing: gap, + runSpacing: gap, + children: [ + for (var index = 0; index < total; index++) + _VoiceProgressCell( + filled: _gridOrder(index, total) < filled, + size: cellSize, + ), + ], ), - ], - ), + ), + ); + }, ); } static int _gridOrder(int index, int total) => ((index + 7) * 29) % total; } +class _VoiceProgressCell extends StatelessWidget { + const _VoiceProgressCell({required this.filled, required this.size}); + + final bool filled; + final double size; + + @override + Widget build(BuildContext context) { + return AnimatedContainer( + duration: const Duration(milliseconds: 180), + curve: Curves.easeOutCubic, + width: size, + height: size, + decoration: BoxDecoration( + color: filled + ? const Color(0xFFFF2D75) + : Colors.white.withValues(alpha: 0.11), + borderRadius: BorderRadius.circular(3), + boxShadow: filled + ? [ + BoxShadow( + color: const Color(0xFFFF2D75).withValues(alpha: 0.34), + blurRadius: 12, + spreadRadius: 1, + ), + ] + : null, + ), + ); + } +} + class _VoiceRecordButton extends StatefulWidget { const _VoiceRecordButton({ required this.progress, @@ -1252,8 +1286,8 @@ class _VoiceRecordButtonState extends State<_VoiceRecordButton> @override Widget build(BuildContext context) { return SizedBox( - width: 164, - height: 164, + width: 220, + height: 220, child: AnimatedBuilder( animation: _pulseController, builder: (context, child) { @@ -1266,11 +1300,11 @@ class _VoiceRecordButtonState extends State<_VoiceRecordButton> Transform.scale( scale: 1 + - ((pulse + offset) % 1) * (0.10 + level * 0.32) + - level * 0.10, + ((pulse + offset) % 1) * (0.20 + level * 0.44) + + level * 0.16, child: Container( - width: 130, - height: 130, + width: 150, + height: 150, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( @@ -1286,11 +1320,11 @@ class _VoiceRecordButtonState extends State<_VoiceRecordButton> ), ), SizedBox( - width: 154, - height: 154, + width: 174, + height: 174, child: CircularProgressIndicator( value: widget.progress, - strokeWidth: 7, + strokeWidth: 8, strokeCap: StrokeCap.round, color: const Color(0xFFFF2D75), backgroundColor: Colors.white.withValues(alpha: 0.12), @@ -1301,8 +1335,8 @@ class _VoiceRecordButtonState extends State<_VoiceRecordButton> ); }, child: SizedBox( - width: 120, - height: 120, + width: 132, + height: 132, child: FilledButton( onPressed: widget.enabled ? widget.onPressed : null, style: FilledButton.styleFrom( @@ -1347,31 +1381,31 @@ class _LibraryWaveSurface extends StatelessWidget { return Stack( alignment: Alignment.center, children: [ - DecoratedBox( - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: const Color( - 0xFFFF2D75, - ).withValues(alpha: active ? 0.20 + liveLevel * 0.18 : 0.06), - blurRadius: 110, - spreadRadius: 24, - ), - BoxShadow( - color: const Color( - 0xFF38F5D3, - ).withValues(alpha: active ? 0.10 + liveLevel * 0.12 : 0.04), - blurRadius: 130, - spreadRadius: 14, - ), - ], + 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, + ), + ], + ), ), - child: const SizedBox.square(dimension: 210), ), Positioned.fill( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.symmetric(horizontal: 0), child: active ? AnimatedWaveList( key: ValueKey(controller.startTime), @@ -1404,7 +1438,7 @@ class _VoiceWaveBar extends StatelessWidget { @override Widget build(BuildContext context) { final level = _amplitudeLevel(amplitude.current); - final height = 14 + level * 210; + final height = 18 + level * 180; final color = Color.lerp( Colors.white.withValues(alpha: 0.28), const Color(0xFFFF2D75), @@ -1417,9 +1451,9 @@ class _VoiceWaveBar extends StatelessWidget { child: Align( alignment: Alignment.center, child: Container( - width: 5, + width: 6, height: height, - margin: const EdgeInsets.symmetric(horizontal: 2.5), + margin: const EdgeInsets.symmetric(horizontal: 3), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), gradient: LinearGradient( @@ -1465,15 +1499,15 @@ class _IdleWaveBars extends StatelessWidget { return Center( child: SizedBox( - height: 220, + height: 210, child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: List.generate(26, (index) { final distance = (index - 12.5).abs(); - final height = 18 + math.max(0.0, 1 - distance / 13) * 56; + final height = 22 + math.max(0.0, 1 - distance / 13) * 94; return Container( - width: 4, + width: 6, height: height, margin: const EdgeInsets.symmetric(horizontal: 3), decoration: BoxDecoration(