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),
],
_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(