Expand voice recording layout
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m7s

This commit is contained in:
Ruslan Bakiev
2026-05-13 17:50:17 +07:00
parent 729dd21b78
commit 04fa49737d

View File

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