Expand voice recording layout
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m7s
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m7s
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user