Make voice grid visibly animate
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m6s
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m6s
This commit is contained in:
@@ -737,6 +737,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
|
||||
Future<List<PlaceRecommendation>>? _nearbyPlacesFuture;
|
||||
StreamSubscription<Amplitude>? _amplitudeSub;
|
||||
Timer? _visualTimer;
|
||||
var _step = 0;
|
||||
var _informationUnits = 0.0;
|
||||
var _recording = false;
|
||||
@@ -745,7 +746,9 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
var _noiseDb = -72.0;
|
||||
var _voicePeakDb = -34.0;
|
||||
var _liveLevel = 0.0;
|
||||
var _visualPhase = 0.0;
|
||||
DateTime? _lastInformationAt;
|
||||
DateTime? _lastAmplitudeAt;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -755,6 +758,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
@override
|
||||
void dispose() {
|
||||
_amplitudeSub?.cancel();
|
||||
_visualTimer?.cancel();
|
||||
_waveController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -784,7 +788,9 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
await _waveController.startRecording();
|
||||
await _amplitudeSub?.cancel();
|
||||
_lastInformationAt = DateTime.now();
|
||||
_lastAmplitudeAt = null;
|
||||
_amplitudeSub = _waveController.amplitudeStream.listen(_handleAmplitude);
|
||||
_startVisualTick();
|
||||
|
||||
setState(() {
|
||||
_micAllowed = true;
|
||||
@@ -798,8 +804,11 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
Future<void> _stopRecording() async {
|
||||
await _amplitudeSub?.cancel();
|
||||
_amplitudeSub = null;
|
||||
_visualTimer?.cancel();
|
||||
_visualTimer = null;
|
||||
await _waveController.stopRecording();
|
||||
_lastInformationAt = null;
|
||||
_lastAmplitudeAt = null;
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
@@ -813,12 +822,16 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
final currentDb = amplitude.current;
|
||||
final now = DateTime.now();
|
||||
final level = _normalizeDbLevel(currentDb);
|
||||
final informationDelta = _consumeInformationDelta(level, now);
|
||||
final informationLevel = currentDb > -64
|
||||
? math.max(0.22, level)
|
||||
: level * 0.35;
|
||||
final informationDelta = _consumeInformationDelta(informationLevel, now);
|
||||
_lastAmplitudeAt = now;
|
||||
|
||||
setState(() {
|
||||
_voiceSamples
|
||||
..removeAt(0)
|
||||
..add(level);
|
||||
..add(math.max(0.04, level));
|
||||
_liveLevel = _smoothLevel(_liveLevel, level);
|
||||
_informationUnits = math.min(
|
||||
_minimumInformationUnits,
|
||||
@@ -830,6 +843,30 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
.setReviewDuration(_waveController.timeElapsed);
|
||||
}
|
||||
|
||||
void _startVisualTick() {
|
||||
_visualTimer?.cancel();
|
||||
_visualTimer = Timer.periodic(const Duration(milliseconds: 70), (_) {
|
||||
if (!mounted || !_recording) {
|
||||
return;
|
||||
}
|
||||
|
||||
_visualPhase += 0.34;
|
||||
final hasFreshAmplitude =
|
||||
_lastAmplitudeAt != null &&
|
||||
DateTime.now().difference(_lastAmplitudeAt!).inMilliseconds < 160;
|
||||
final baseLevel = hasFreshAmplitude ? _liveLevel : 0.10;
|
||||
final wave =
|
||||
(math.sin(_visualPhase) * 0.5 + 0.5) * (0.16 + baseLevel * 0.34);
|
||||
final visualLevel = (0.06 + baseLevel * 0.70 + wave).clamp(0.0, 1.0);
|
||||
|
||||
setState(() {
|
||||
_voiceSamples
|
||||
..removeAt(0)
|
||||
..add(visualLevel);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
double _normalizeDbLevel(double currentDb) {
|
||||
final db = currentDb.clamp(-160.0, 0.0);
|
||||
if (db < _noiseDb) {
|
||||
@@ -1095,7 +1132,7 @@ class _VoiceStep extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final canFinish = canContinue && !isRecording;
|
||||
final canFinish = canContinue;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
@@ -1392,7 +1429,8 @@ class _VoiceInformationPainter extends CustomPainter {
|
||||
final signal = samples.isEmpty
|
||||
? 0.0
|
||||
: samples[sampleIndex].clamp(0.0, 1.0);
|
||||
final filled = _cellOrder(cellIndex, totalCells) < activeCells;
|
||||
final fillOrder = (rows - 1 - row) * columns + column;
|
||||
final filled = fillOrder < activeCells;
|
||||
final x = startX + column * (cellSize + gap);
|
||||
final y = startY + row * (cellSize + gap);
|
||||
final rect = RRect.fromRectAndRadius(
|
||||
@@ -1442,8 +1480,6 @@ class _VoiceInformationPainter extends CustomPainter {
|
||||
}
|
||||
}
|
||||
|
||||
int _cellOrder(int index, int total) => ((index + 11) * 73) % total;
|
||||
|
||||
double _hashUnit(int index) {
|
||||
final value = math.sin(index * 12.9898 + 78.233) * 43758.5453;
|
||||
return value - value.floorToDouble();
|
||||
|
||||
Reference in New Issue
Block a user