* Update copyright holding and dates * compound IDE Macros updated * update copyright * update copyrights done * update templates and README
124 lines
4.4 KiB
Swift
124 lines
4.4 KiB
Swift
//
|
|
// Copyright 2025 Element Creations Ltd.
|
|
// Copyright 2023-2025 New Vector Ltd.
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
// Please see LICENSE files in the repository root for full details.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct EstimatedWaveform: Equatable, Hashable {
|
|
static let dataRange: ClosedRange<UInt16> = 0...1024
|
|
let data: [UInt16]
|
|
}
|
|
|
|
extension EstimatedWaveform {
|
|
/// Maps the `data` array to Float values in the range 0...1 respecting the `Self.dataRange` limits.
|
|
/// Up to `maxSamplesCount` will be returned in the output array
|
|
func normalisedData(maxSamplesCount: Int) -> [Float] {
|
|
guard maxSamplesCount > 0 else {
|
|
return []
|
|
}
|
|
|
|
// Filter the data to keep only the expected number of samples
|
|
let result: [UInt16]
|
|
if data.count > maxSamplesCount {
|
|
result = (0..<maxSamplesCount)
|
|
.map { index in
|
|
let targetIndex = Int((Double(index) * (Double(data.count) / Double(maxSamplesCount))).rounded())
|
|
return UInt16(data[targetIndex])
|
|
}
|
|
} else {
|
|
result = data
|
|
}
|
|
|
|
// Normalize the sample in the allowed range
|
|
return result.map { Float($0) / Float(Self.dataRange.upperBound) }
|
|
}
|
|
}
|
|
|
|
extension EstimatedWaveform {
|
|
static let mockWaveform = EstimatedWaveform(data: [0, 0, 0, 3, 3, 127, 400, 266, 126, 122, 373, 251, 45, 112,
|
|
334, 205, 99, 138, 397, 354, 125, 361, 199, 51,
|
|
294, 131, 19, 2, 3, 3, 1, 2, 0, 0,
|
|
0, 0])
|
|
}
|
|
|
|
struct EstimatedWaveformView: View {
|
|
var lineWidth: CGFloat = 2
|
|
var linePadding: CGFloat = 2
|
|
var waveform: EstimatedWaveform
|
|
var progress: CGFloat = 0.0
|
|
|
|
@State private var normalizedWaveformData: [Float] = []
|
|
|
|
var body: some View {
|
|
GeometryReader { geometry in
|
|
WaveformShape(lineWidth: lineWidth,
|
|
linePadding: linePadding,
|
|
waveformData: normalizedWaveformData)
|
|
.stroke(Color.compound.iconSecondary, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
|
|
.progressMask(progress: progress)
|
|
.preference(key: ViewSizeKey.self, value: geometry.size)
|
|
}
|
|
.onPreferenceChange(ViewSizeKey.self) { size in
|
|
buildNormalizedWaveformData(size: size)
|
|
}
|
|
}
|
|
|
|
private func buildNormalizedWaveformData(size: CGSize) {
|
|
let count = Int(size.width / (lineWidth + linePadding))
|
|
// Rebuild the normalized waveform data only if the count has changed
|
|
if normalizedWaveformData.count == count {
|
|
return
|
|
}
|
|
normalizedWaveformData = waveform.normalisedData(maxSamplesCount: count)
|
|
}
|
|
}
|
|
|
|
private struct ViewSizeKey: PreferenceKey {
|
|
static var defaultValue: CGSize = .zero
|
|
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
|
|
value = nextValue()
|
|
}
|
|
}
|
|
|
|
private struct WaveformShape: Shape {
|
|
let lineWidth: CGFloat
|
|
let linePadding: CGFloat
|
|
let waveformData: [Float]
|
|
var minimumGraphAmplitude: CGFloat = 1.0
|
|
|
|
func path(in rect: CGRect) -> Path {
|
|
let width = rect.size.width
|
|
let height = rect.size.height
|
|
let centerY = rect.size.height / 2
|
|
var xOffset: CGFloat = lineWidth / 2
|
|
var index = 0
|
|
|
|
var path = Path()
|
|
while xOffset <= width {
|
|
let sample = CGFloat(index >= waveformData.count ? 0 : waveformData[index])
|
|
let drawingAmplitude = max(minimumGraphAmplitude, sample * (height - 2))
|
|
|
|
path.move(to: CGPoint(x: xOffset, y: centerY - drawingAmplitude / 2))
|
|
path.addLine(to: CGPoint(x: xOffset, y: centerY + drawingAmplitude / 2))
|
|
xOffset += lineWidth + linePadding
|
|
index += 1
|
|
}
|
|
|
|
return path
|
|
}
|
|
}
|
|
|
|
struct EstimatedWaveformView_Previews: PreviewProvider, TestablePreview {
|
|
static var previews: some View {
|
|
// Wrap the WaveformView in a VStack otherwise the preview test will fail (because of Prefire / GeometryReader)
|
|
VStack(spacing: 0) {
|
|
EstimatedWaveformView(waveform: EstimatedWaveform.mockWaveform, progress: 0.5)
|
|
.frame(width: 140, height: 50)
|
|
}
|
|
}
|
|
}
|