Hide voice waveform visualization
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m39s
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m39s
This commit is contained in:
@@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:latlong2/latlong.dart' hide Path;
|
||||
import 'package:waveform_flutter/waveform_flutter.dart';
|
||||
import 'package:waveform_flutter/waveform_flutter.dart' show Amplitude;
|
||||
import 'package:waveform_recorder/waveform_recorder.dart';
|
||||
|
||||
import '../api/mapflow_api.dart';
|
||||
@@ -870,7 +870,6 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
isRecording: _recording,
|
||||
isSubmitting: _submitting,
|
||||
micAllowed: _micAllowed,
|
||||
waveController: _waveController,
|
||||
liveLevel: _liveLevel,
|
||||
canContinue: widget.hasTelegramAuth && informationProgress >= 1,
|
||||
onToggleRecording: _toggleRecording,
|
||||
@@ -1069,7 +1068,6 @@ class _VoiceStep extends StatelessWidget {
|
||||
required this.isRecording,
|
||||
required this.isSubmitting,
|
||||
required this.micAllowed,
|
||||
required this.waveController,
|
||||
required this.liveLevel,
|
||||
required this.canContinue,
|
||||
required this.onToggleRecording,
|
||||
@@ -1082,7 +1080,6 @@ class _VoiceStep extends StatelessWidget {
|
||||
final bool isRecording;
|
||||
final bool isSubmitting;
|
||||
final bool micAllowed;
|
||||
final WaveformRecorderController waveController;
|
||||
final double liveLevel;
|
||||
final bool canContinue;
|
||||
final Future<void> Function() onToggleRecording;
|
||||
@@ -1106,23 +1103,7 @@ class _VoiceStep extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: _VoiceProgressGrid(progress: informationProgress),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: _LibraryWaveSurface(
|
||||
controller: waveController,
|
||||
active: isRecording,
|
||||
progress: informationProgress,
|
||||
liveLevel: liveLevel,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(child: _VoiceProgressGrid(progress: informationProgress)),
|
||||
if (!micAllowed)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 10),
|
||||
@@ -1152,20 +1133,20 @@ class _VoiceProgressGrid extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const columns = 20;
|
||||
const rows = 10;
|
||||
const columns = 24;
|
||||
const rows = 14;
|
||||
const total = columns * rows;
|
||||
final filled = (progress.clamp(0.0, 1.0) * total).round();
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
const gap = 5.0;
|
||||
const gap = 4.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),
|
||||
math.min(maxCellWidth, maxCellHeight).clamp(5.0, 16.0),
|
||||
);
|
||||
final gridWidth = columns * cellSize + gap * (columns - 1);
|
||||
final gridHeight = rows * cellSize + gap * (rows - 1);
|
||||
@@ -1180,7 +1161,7 @@ class _VoiceProgressGrid extends StatelessWidget {
|
||||
children: [
|
||||
for (var index = 0; index < total; index++)
|
||||
_VoiceProgressCell(
|
||||
filled: _gridOrder(index, total) < filled,
|
||||
filled: _gridOrder(index, columns, rows) < filled,
|
||||
size: cellSize,
|
||||
),
|
||||
],
|
||||
@@ -1191,7 +1172,20 @@ class _VoiceProgressGrid extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
static int _gridOrder(int index, int total) => ((index + 7) * 29) % total;
|
||||
static int _gridOrder(int index, int columns, int rows) {
|
||||
final row = index ~/ columns;
|
||||
final column = index % columns;
|
||||
final centerX = (columns - 1) / 2;
|
||||
final centerY = (rows - 1) / 2;
|
||||
final dx = column - centerX;
|
||||
final dy = row - centerY;
|
||||
final radius = math.sqrt(dx * dx + dy * dy);
|
||||
final angle = math.atan2(dy, dx);
|
||||
final shell = (radius * 7.0 + angle * 5.0).floor();
|
||||
final noise = (math.sin((column + 1) * 37.17 + (row + 1) * 91.43) * 10000)
|
||||
.abs();
|
||||
return ((shell * 31 + noise.floor()) % (columns * rows));
|
||||
}
|
||||
}
|
||||
|
||||
class _VoiceProgressCell extends StatelessWidget {
|
||||
@@ -1363,165 +1357,6 @@ class _VoiceRecordButtonState extends State<_VoiceRecordButton>
|
||||
}
|
||||
}
|
||||
|
||||
class _LibraryWaveSurface extends StatelessWidget {
|
||||
const _LibraryWaveSurface({
|
||||
required this.controller,
|
||||
required this.active,
|
||||
required this.progress,
|
||||
required this.liveLevel,
|
||||
});
|
||||
|
||||
final WaveformRecorderController controller;
|
||||
final bool active;
|
||||
final double progress;
|
||||
final double liveLevel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||||
child: active
|
||||
? AnimatedWaveList(
|
||||
key: ValueKey(controller.startTime),
|
||||
stream: controller.amplitudeStream,
|
||||
barBuilder: (animation, amplitude) => _VoiceWaveBar(
|
||||
animation: animation,
|
||||
amplitude: amplitude,
|
||||
progress: progress,
|
||||
),
|
||||
)
|
||||
: _IdleWaveBars(progress: progress),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _VoiceWaveBar extends StatelessWidget {
|
||||
const _VoiceWaveBar({
|
||||
required this.animation,
|
||||
required this.amplitude,
|
||||
required this.progress,
|
||||
});
|
||||
|
||||
final Animation<double> animation;
|
||||
final Amplitude amplitude;
|
||||
final double progress;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final level = _amplitudeLevel(amplitude.current);
|
||||
final height = 18 + level * 180;
|
||||
final color = Color.lerp(
|
||||
Colors.white.withValues(alpha: 0.28),
|
||||
const Color(0xFFFF2D75),
|
||||
progress.clamp(0.0, 1.0),
|
||||
)!;
|
||||
|
||||
return SizeTransition(
|
||||
sizeFactor: animation,
|
||||
axis: Axis.horizontal,
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
width: 6,
|
||||
height: height,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter,
|
||||
colors: [
|
||||
color.withValues(alpha: 0.42),
|
||||
color,
|
||||
const Color(0xFFFF7A90),
|
||||
],
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: color.withValues(alpha: 0.38),
|
||||
blurRadius: 18,
|
||||
spreadRadius: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double _amplitudeLevel(double db) {
|
||||
final normalized = ((db.clamp(-76.0, -6.0) + 76.0) / 70.0).clamp(0.0, 1.0);
|
||||
return math.pow(normalized, 0.62).toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
class _IdleWaveBars extends StatelessWidget {
|
||||
const _IdleWaveBars({required this.progress});
|
||||
|
||||
final double progress;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = Color.lerp(
|
||||
Colors.white.withValues(alpha: 0.18),
|
||||
const Color(0xFFFF2D75),
|
||||
progress.clamp(0.0, 1.0),
|
||||
)!;
|
||||
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
height: 210,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: List.generate(26, (index) {
|
||||
final distance = (index - 12.5).abs();
|
||||
final height = 22 + math.max(0.0, 1 - distance / 13) * 94;
|
||||
return Container(
|
||||
width: 6,
|
||||
height: height,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StepLayout extends StatelessWidget {
|
||||
const _StepLayout({required this.body, this.action});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user