diff --git a/VoiceInk/Views/Recorder/AudioVisualizerView.swift b/VoiceInk/Views/Recorder/AudioVisualizerView.swift index eb678a6..9a2c795 100644 --- a/VoiceInk/Views/Recorder/AudioVisualizerView.swift +++ b/VoiceInk/Views/Recorder/AudioVisualizerView.swift @@ -4,110 +4,126 @@ struct AudioVisualizer: View { let audioMeter: AudioMeter let color: Color let isActive: Bool - - private let barCount = 12 - private let minHeight: CGFloat = 5 - private let maxHeight: CGFloat = 32 - private let barWidth: CGFloat = 3.5 - private let barSpacing: CGFloat = 2.3 - private let hardThreshold: Double = 0.3 - - private let sensitivityMultipliers: [Double] - - @State private var barHeights: [CGFloat] - @State private var targetHeights: [CGFloat] - + + private let barCount = 15 + private let barWidth: CGFloat = 3 + private let barSpacing: CGFloat = 2 + private let minHeight: CGFloat = 4 + private let maxHeight: CGFloat = 28 + + private let phases: [Double] + + @State private var heights: [CGFloat] + init(audioMeter: AudioMeter, color: Color, isActive: Bool) { self.audioMeter = audioMeter self.color = color self.isActive = isActive - - self.sensitivityMultipliers = (0.. 0.5 { - withAnimation( - isDecaying - ? .spring(response: 0.4, dampingFraction: 0.8) - : .spring(response: 0.3, dampingFraction: 0.7) - ) { - barHeights[i] = targetHeights[i] - } - } - } - } - - private func resetBars() { - withAnimation(.easeOut(duration: 0.15)) { - barHeights = Array(repeating: minHeight, count: barCount) - targetHeights = Array(repeating: minHeight, count: barCount) + + private func resetWave() { + withAnimation(.easeOut(duration: 0.2)) { + heights = Array(repeating: minHeight, count: barCount) } } } struct StaticVisualizer: View { - private let barCount = 12 - private let barWidth: CGFloat = 3.5 - private let staticHeight: CGFloat = 5.0 - private let barSpacing: CGFloat = 2.3 + // Match AudioVisualizer dimensions + private let barCount = 15 + private let barWidth: CGFloat = 3 + private let staticHeight: CGFloat = 4 + private let barSpacing: CGFloat = 2 let color: Color - + var body: some View { HStack(spacing: barSpacing) { - ForEach(0..= 5 { currentDot = -1 } - } + startAnimation() } .onDisappear { timer?.invalidate() timer = nil } } + + private func startAnimation() { + timer?.invalidate() + currentDot = 0 + timer = Timer.scheduledTimer(withTimeInterval: animationSpeed, repeats: true) { _ in + currentDot = (currentDot + 1) % (dotCount + 2) + if currentDot > dotCount { currentDot = -1 } + } + } } // MARK: - Prompt Button Component @@ -276,35 +293,21 @@ struct RecorderStatusDisplay: View { let currentState: RecordingState let audioMeter: AudioMeter let menuBarHeight: CGFloat? - + init(currentState: RecordingState, audioMeter: AudioMeter, menuBarHeight: CGFloat? = nil) { self.currentState = currentState self.audioMeter = audioMeter self.menuBarHeight = menuBarHeight } - + var body: some View { Group { if currentState == .enhancing { - VStack(spacing: 2) { - Text("Enhancing") - .foregroundColor(.white) - .font(.system(size: 11, weight: .medium, design: .default)) - .lineLimit(1) - .minimumScaleFactor(0.5) - - ProgressAnimation(animationSpeed: 0.15) - } + ProcessingStatusDisplay(mode: .enhancing, color: .white) + .transition(.opacity) } else if currentState == .transcribing { - VStack(spacing: 2) { - Text("Transcribing") - .foregroundColor(.white) - .font(.system(size: 11, weight: .medium, design: .default)) - .lineLimit(1) - .minimumScaleFactor(0.5) - - ProgressAnimation(animationSpeed: 0.12) - } + ProcessingStatusDisplay(mode: .transcribing, color: .white) + .transition(.opacity) } else if currentState == .recording { AudioVisualizer( audioMeter: audioMeter, @@ -312,10 +315,13 @@ struct RecorderStatusDisplay: View { isActive: currentState == .recording ) .scaleEffect(y: menuBarHeight != nil ? min(1.0, (menuBarHeight! - 8) / 25) : 1.0, anchor: .center) + .transition(.opacity) } else { StaticVisualizer(color: .white) .scaleEffect(y: menuBarHeight != nil ? min(1.0, (menuBarHeight! - 8) / 25) : 1.0, anchor: .center) + .transition(.opacity) } } + .animation(.easeInOut(duration: 0.2), value: currentState) } }