import SwiftUI /// Canvas-based waveform visualization with playback progress. struct WaveformView: View { @Environment(PlayerViewModel.self) private var playerVM @EnvironmentObject private var theme: AppTheme @State private var isDragging = false @State private var dragProgress: Double = 0 var body: some View { GeometryReader { geo in Canvas { context, size in let samples = playerVM.waveformSamples guard !samples.isEmpty else { return } let barWidth = size.width / CGFloat(samples.count) let midY = size.height / 2 let progress = isDragging ? dragProgress : playerVM.progress for (index, sample) in samples.enumerated() { let x = CGFloat(index) * barWidth let isPlayed = Double(index) / Double(samples.count) < progress let topHeight = CGFloat(sample.max) * midY let bottomHeight = CGFloat(-sample.min) * midY let rect = CGRect( x: x, y: midY - topHeight, width: max(barWidth - 0.5, 0.5), height: topHeight + bottomHeight ) let color = isPlayed ? theme.seekbarForeground : theme.seekbarBackground context.fill(Path(rect), with: .color(color)) } // Playhead line let playheadX = progress * Double(size.width) let playheadRect = CGRect(x: playheadX - 0.5, y: 0, width: 1, height: size.height) context.fill(Path(playheadRect), with: .color(theme.accent)) } .gesture( DragGesture(minimumDistance: 0) .onChanged { value in isDragging = true dragProgress = max(0, min(1, Double(value.location.x / geo.size.width))) } .onEnded { value in let prog = max(0, min(1, Double(value.location.x / geo.size.width))) playerVM.seekToProgress(prog) isDragging = false } ) } .clipShape(RoundedRectangle(cornerRadius: 6)) .contentShape(Rectangle()) } }