Files
voice-cat/clients/apple/macOS/VoiceCatMac/Sheets/PerUserTuningSheet.swift

124 lines
4.8 KiB
Swift
Raw Normal View History

import AppKit
import VoiceCatCore
final class PerUserTuningSheet: NSViewController {
private let client: VoiceCatClient
private let userId: UInt32
private let nickname: String
private var streams: [StreamSummary] = []
private var rows: [StreamRow] = []
private struct StreamRow {
let streamId: UInt32
let label: String
let gainSlider: NSSlider
let muteCheckbox: NSButton
let nrCheckbox: NSButton
}
init(client: VoiceCatClient, userId: UInt32, nickname: String) {
self.client = client
self.userId = userId
self.nickname = nickname
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError() }
override func loadView() {
view = NSView(frame: NSRect(x: 0, y: 0, width: 400, height: 200))
}
override func viewDidLoad() {
super.viewDidLoad()
streams = client.listUserStreams(userId)
buildUI()
}
private func buildUI() {
let titleLabel = NSTextField(labelWithString: "Volume & Noise Settings — \(nickname)")
titleLabel.font = .boldSystemFont(ofSize: 13)
var rowViews: [NSView] = [titleLabel]
for stream in streams {
let (_, state) = client.getRemoteStream(userId: userId, streamId: stream.id)
let gain = state?.gain ?? 1.0
let muted = state?.muted ?? false
let nr = state?.noiseReduction ?? false
let gainSlider = NSSlider(value: Double(gain * 100), minValue: 0, maxValue: 200, target: self, action: #selector(sliderChanged))
gainSlider.tag = Int(stream.id)
gainSlider.numberOfTickMarks = 0
gainSlider.setAccessibilityLabel("Volume for \(stream.label): \(Int(gain * 100)) percent")
let muteCheckbox = NSButton(checkboxWithTitle: "Mute", target: self, action: #selector(muteChanged))
muteCheckbox.state = muted ? .on : .off
muteCheckbox.tag = Int(stream.id)
muteCheckbox.setAccessibilityLabel("Mute \(stream.label)")
let nrCheckbox = NSButton(checkboxWithTitle: "Noise reduction", target: self, action: #selector(nrChanged))
nrCheckbox.state = nr ? .on : .off
nrCheckbox.tag = Int(stream.id)
nrCheckbox.setAccessibilityLabel("Noise reduction for \(stream.label)")
let streamLabel = NSTextField(labelWithString: "\(stream.label):")
let gainLabel = NSTextField(labelWithString: "Volume:")
let row = NSStackView(views: [streamLabel, gainLabel, gainSlider, muteCheckbox, nrCheckbox])
row.orientation = .horizontal; row.spacing = 8
rowViews.append(row)
rows.append(StreamRow(streamId: stream.id, label: stream.label,
gainSlider: gainSlider, muteCheckbox: muteCheckbox,
nrCheckbox: nrCheckbox))
}
if streams.isEmpty {
rowViews.append(NSTextField(labelWithString: "This user has no active streams."))
}
let doneButton = NSButton(title: "Done", target: self, action: #selector(doneClicked))
doneButton.bezelStyle = .rounded; doneButton.keyEquivalent = "\r"
doneButton.setAccessibilityLabel("Close settings")
rowViews.append(NSStackView(views: [NSView(), doneButton]))
let stack = NSStackView(views: rowViews)
stack.orientation = .vertical
stack.spacing = 10
stack.edgeInsets = NSEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: view.topAnchor),
stack.leadingAnchor.constraint(equalTo: view.leadingAnchor),
stack.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
}
@objc private func sliderChanged(_ sender: NSSlider) {
apply(streamId: UInt32(sender.tag))
}
@objc private func muteChanged(_ sender: NSButton) {
apply(streamId: UInt32(sender.tag))
}
@objc private func nrChanged(_ sender: NSButton) {
apply(streamId: UInt32(sender.tag))
}
private func apply(streamId: UInt32) {
guard let row = rows.first(where: { $0.streamId == streamId }) else { return }
let gain = Float(row.gainSlider.doubleValue) / 100.0
let muted = row.muteCheckbox.state == .on
let nr = row.nrCheckbox.state == .on
client.setRemoteStream(userId: userId, streamId: streamId,
gain: gain, muted: muted, noiseReduction: nr)
row.gainSlider.setAccessibilityLabel("Volume for \(row.label): \(Int(row.gainSlider.doubleValue)) percent")
}
@objc private func doneClicked() { dismiss(nil) }
}