Files
meetingnotes/meetingnotes/Views/AudioLevelWindow.swift
T

165 lines
6.2 KiB
Swift

import SwiftUI
// MARK: - Dancing Audio Bars
struct DancingAudioBars: View {
let micLevel: Float
let systemLevel: Float
var body: some View {
VStack(spacing: 0) {
// App logo
Image("Icon32")
.resizable()
.frame(width: 32, height: 32)
// Interleaved bars - alternating blue (mic) and orange (system)
HStack(spacing: -2) {
// Bar 1: Microphone (blue)
RoundedRectangle(cornerRadius: 2)
.fill(Color.blue.opacity(0.6))
.frame(width: 6, height: getDancingBarHeight(index: 0, level: micLevel))
.animation(.easeInOut(duration: 0.1), value: micLevel)
// Bar 2: System (orange)
RoundedRectangle(cornerRadius: 2)
.fill(Color.orange.opacity(0.6))
.frame(width: 6, height: getDancingBarHeight(index: 1, level: systemLevel))
.animation(.easeInOut(duration: 0.1).delay(0.03), value: systemLevel)
// Bar 3: Microphone (blue)
RoundedRectangle(cornerRadius: 2)
.fill(Color.blue.opacity(0.6))
.frame(width: 6, height: getDancingBarHeight(index: 2, level: micLevel))
.animation(.easeInOut(duration: 0.1).delay(0.06), value: micLevel)
// Bar 4: System (orange)
RoundedRectangle(cornerRadius: 2)
.fill(Color.orange.opacity(0.6))
.frame(width: 6, height: getDancingBarHeight(index: 0, level: systemLevel))
.animation(.easeInOut(duration: 0.1).delay(0.09), value: systemLevel)
// Bar 5: Microphone (blue)
RoundedRectangle(cornerRadius: 2)
.fill(Color.blue.opacity(0.6))
.frame(width: 6, height: getDancingBarHeight(index: 1, level: micLevel))
.animation(.easeInOut(duration: 0.1).delay(0.12), value: micLevel)
// Bar 6: System (orange)
RoundedRectangle(cornerRadius: 2)
.fill(Color.orange.opacity(0.6))
.frame(width: 6, height: getDancingBarHeight(index: 2, level: systemLevel))
.animation(.easeInOut(duration: 0.1).delay(0.15), value: systemLevel)
}
.frame(width: 32, height: 32)
}
.padding(0)
}
private func getDancingBarHeight(index: Int, level: Float) -> CGFloat {
let baseHeight: CGFloat = 4 // Flat when no audio
let maxHeight: CGFloat = 24 // Max dancing height
if level > 0.03 { // Only dance when there's actual audio
// Each bar responds to the same audio level but with different scaling
// This creates a natural dancing effect as audio levels fluctuate
let barVariation: [CGFloat] = [0.9, 1.0, 0.8] // Different responsiveness per bar
let audioResponse = CGFloat(level) * maxHeight * barVariation[index % 3]
return max(baseHeight, min(maxHeight, baseHeight + audioResponse))
}
return baseHeight
}
}
// MARK: - Audio Level Window View
struct AudioLevelWindowView: View {
@StateObject private var audioLevelManager = AudioLevelManager.shared
var body: some View {
let micLevel = audioLevelManager.isRecording ? audioLevelManager.micAudioLevel * 40 : 0
let systemLevel = audioLevelManager.isRecording ? audioLevelManager.systemAudioLevel * 5 : 0
return DancingAudioBars(
micLevel: micLevel,
systemLevel: systemLevel
)
.padding(2)
.background(.regularMaterial.opacity(0.9), in: RoundedRectangle(cornerRadius: 10))
.shadow(color: .black.opacity(0.2), radius: 3)
}
}
// MARK: - Audio Level Manager (Singleton to share data)
class AudioLevelManager: ObservableObject {
static let shared = AudioLevelManager()
@Published var micAudioLevel: Float = 0.0
@Published var systemAudioLevel: Float = 0.0
@Published var isRecording: Bool = false
private init() {}
func updateMicLevel(_ level: Float) {
DispatchQueue.main.async {
self.micAudioLevel = level
}
}
func updateSystemLevel(_ level: Float) {
DispatchQueue.main.async {
self.systemAudioLevel = level
}
}
func updateRecordingState(_ isRecording: Bool) {
DispatchQueue.main.async {
self.isRecording = isRecording
if isRecording {
// Auto-show the window when recording starts
AudioLevelWindowManager.shared.showWindow()
} else {
// Auto-hide the window when recording stops
AudioLevelWindowManager.shared.hideWindow()
}
}
}
}
// MARK: - Audio Level Window Manager
@MainActor
class AudioLevelWindowManager: ObservableObject {
static let shared = AudioLevelWindowManager()
private init() {}
func showWindow() {
// Find the audio levels window and bring it to front
for window in NSApplication.shared.windows {
if window.identifier?.rawValue == "audio-levels" {
window.orderFront(nil)
window.level = .floating
window.collectionBehavior = [.canJoinAllSpaces, .stationary, .ignoresCycle]
window.isMovableByWindowBackground = true
window.hasShadow = true
window.styleMask = [.borderless]
return
}
}
// If window not found, post a notification to trigger opening
NotificationCenter.default.post(name: .openAudioLevelWindow, object: nil)
}
func hideWindow() {
// Find the audio levels window and hide it
for window in NSApplication.shared.windows {
if window.identifier?.rawValue == "audio-levels" {
window.orderOut(nil)
return
}
}
}
}
extension Notification.Name {
static let openAudioLevelWindow = Notification.Name("openAudioLevelWindow")
}