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_map/flutter_map.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:latlong2/latlong.dart' hide Path;
|
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 'package:waveform_recorder/waveform_recorder.dart';
|
||||||
|
|
||||||
import '../api/mapflow_api.dart';
|
import '../api/mapflow_api.dart';
|
||||||
@@ -870,7 +870,6 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
isRecording: _recording,
|
isRecording: _recording,
|
||||||
isSubmitting: _submitting,
|
isSubmitting: _submitting,
|
||||||
micAllowed: _micAllowed,
|
micAllowed: _micAllowed,
|
||||||
waveController: _waveController,
|
|
||||||
liveLevel: _liveLevel,
|
liveLevel: _liveLevel,
|
||||||
canContinue: widget.hasTelegramAuth && informationProgress >= 1,
|
canContinue: widget.hasTelegramAuth && informationProgress >= 1,
|
||||||
onToggleRecording: _toggleRecording,
|
onToggleRecording: _toggleRecording,
|
||||||
@@ -1069,7 +1068,6 @@ class _VoiceStep extends StatelessWidget {
|
|||||||
required this.isRecording,
|
required this.isRecording,
|
||||||
required this.isSubmitting,
|
required this.isSubmitting,
|
||||||
required this.micAllowed,
|
required this.micAllowed,
|
||||||
required this.waveController,
|
|
||||||
required this.liveLevel,
|
required this.liveLevel,
|
||||||
required this.canContinue,
|
required this.canContinue,
|
||||||
required this.onToggleRecording,
|
required this.onToggleRecording,
|
||||||
@@ -1082,7 +1080,6 @@ class _VoiceStep extends StatelessWidget {
|
|||||||
final bool isRecording;
|
final bool isRecording;
|
||||||
final bool isSubmitting;
|
final bool isSubmitting;
|
||||||
final bool micAllowed;
|
final bool micAllowed;
|
||||||
final WaveformRecorderController waveController;
|
|
||||||
final double liveLevel;
|
final double liveLevel;
|
||||||
final bool canContinue;
|
final bool canContinue;
|
||||||
final Future<void> Function() onToggleRecording;
|
final Future<void> Function() onToggleRecording;
|
||||||
@@ -1106,23 +1103,7 @@ class _VoiceStep extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
],
|
],
|
||||||
Expanded(
|
Expanded(child: _VoiceProgressGrid(progress: informationProgress)),
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!micAllowed)
|
if (!micAllowed)
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.only(bottom: 10),
|
padding: EdgeInsets.only(bottom: 10),
|
||||||
@@ -1152,20 +1133,20 @@ class _VoiceProgressGrid extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const columns = 20;
|
const columns = 24;
|
||||||
const rows = 10;
|
const rows = 14;
|
||||||
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 LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
const gap = 5.0;
|
const gap = 4.0;
|
||||||
final maxCellWidth =
|
final maxCellWidth =
|
||||||
(constraints.maxWidth - gap * (columns - 1)) / columns;
|
(constraints.maxWidth - gap * (columns - 1)) / columns;
|
||||||
final maxCellHeight = (constraints.maxHeight - gap * (rows - 1)) / rows;
|
final maxCellHeight = (constraints.maxHeight - gap * (rows - 1)) / rows;
|
||||||
final cellSize = math.max(
|
final cellSize = math.max(
|
||||||
6.0,
|
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 gridWidth = columns * cellSize + gap * (columns - 1);
|
||||||
final gridHeight = rows * cellSize + gap * (rows - 1);
|
final gridHeight = rows * cellSize + gap * (rows - 1);
|
||||||
@@ -1180,7 +1161,7 @@ class _VoiceProgressGrid extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
for (var index = 0; index < total; index++)
|
for (var index = 0; index < total; index++)
|
||||||
_VoiceProgressCell(
|
_VoiceProgressCell(
|
||||||
filled: _gridOrder(index, total) < filled,
|
filled: _gridOrder(index, columns, rows) < filled,
|
||||||
size: cellSize,
|
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 {
|
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 {
|
class _StepLayout extends StatelessWidget {
|
||||||
const _StepLayout({required this.body, this.action});
|
const _StepLayout({required this.body, this.action});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user