Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions clients/macos/Aura/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct ContentView: View {
@State private var showingProfileEditor = false
@State private var showingChannelEditor = false
@State private var editingChannel: ChannelModel?
@State private var channelPendingDeletion: ChannelModel?
@State private var pttCancellable: AnyCancellable?
@State private var pttErrorMessage: String?

Expand Down Expand Up @@ -58,8 +59,25 @@ struct ContentView: View {
} message: { msg in
Text(msg)
}
.confirmationDialog(
"Delete Channel",
isPresented: Binding(
get: { channelPendingDeletion != nil },
set: { if !$0 { channelPendingDeletion = nil } }
),
presenting: channelPendingDeletion
) { channel in
Button("Delete \"\(channel.name)\"", role: .destructive) {
let id = channel.id
channelPendingDeletion = nil
Task { await client?.deleteChannel(id: id) }
}
Button("Cancel", role: .cancel) { channelPendingDeletion = nil }
} message: { channel in
Text("This permanently deletes \"\(channel.name)\" and removes everyone from it. This cannot be undone.")
}
}

// MARK: - Login View (Centered)

@ViewBuilder
Expand Down Expand Up @@ -969,7 +987,7 @@ struct ContentView: View {
Divider()

Button(role: .destructive, action: {
// TODO: Implement delete
channelPendingDeletion = channel
}) {
Label("Delete Channel", systemImage: "trash")
}
Expand Down
39 changes: 38 additions & 1 deletion clients/macos/Aura/Services/QuicNetworkClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ public class QuicNetworkClient {
private static let MSG_CREATE_CHANNEL: UInt8 = 0x40
private static let MSG_UPDATE_CHANNEL: UInt8 = 0x41
private static let MSG_UPDATE_PROFILE: UInt8 = 0x42
private static let MSG_DELETE_CHANNEL: UInt8 = 0x43
private static let MSG_UPDATE_STATUS: UInt8 = 0x45
private static let MSG_PROFILE_UPDATED: UInt8 = 0x46 // Server → clients broadcast

Expand Down Expand Up @@ -885,7 +886,43 @@ public class QuicNetworkClient {
print("[QuicClient] Failed to send update channel request: \(error)")
}
}


/// Delete a channel (Admin only). The server replies with an `AdminResponse`
/// and broadcasts a fresh server snapshot (`MSG_CHANNEL_STATE`), so the
/// channel list refreshes for everyone without a response handler here.
public func deleteChannel(id: String) async {
guard isAdmin else { return }

// DeleteChannelRequest is a single proto field: `string channel_id = 1`.
// No generated encoder exists for it, so encode the one field inline:
// tag 0x0A (field 1, length-delimited) + varint length + UTF-8 bytes.
let idBytes = Array(id.utf8)
var payload = Data([0x0A])
var len = idBytes.count
repeat {
var byte = UInt8(len & 0x7F)
len >>= 7
if len > 0 { byte |= 0x80 }
payload.append(byte)
} while len > 0
payload.append(contentsOf: idBytes)

let mutStream: NWConnection? = await MainActor.run { self.controlStream }
guard let stream = mutStream else { return }

var msg = Data([Self.MSG_DELETE_CHANNEL]) // MSG_DELETE_CHANNEL
let frameLen = UInt32(payload.count).littleEndian
msg.append(Data(withUnsafeBytes(of: frameLen) { Array($0) }))
msg.append(payload)

do {
try await send(data: msg, on: stream)
print("[QuicClient] Delete channel request sent for \(id)")
} catch {
print("[QuicClient] Failed to send delete channel request: \(error)")
}
}

// MARK: - Keepalive

/// Start periodic keepalive pings to prevent session timeout
Expand Down
41 changes: 38 additions & 3 deletions clients/macos/Aura/Views/AudioSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import SwiftUI

struct AudioSettingsView: View {
@AppStorage("noiseSuppressionEnabled") private var noiseSuppressionEnabled = true
@AppStorage("aecEnabled") private var aecEnabled = false
@AppStorage("webrtcNsEnabled") private var webrtcNsEnabled = false
@AppStorage("webrtcAgcEnabled") private var webrtcAgcEnabled = true
@AppStorage("jitterBufferMs") private var jitterBufferMs = 20

var body: some View {
Form {
Section("Audio Quality") {
Expand All @@ -14,12 +17,44 @@ struct AudioSettingsView: View {
object: ["noiseSuppression": newValue]
)
}

Text("Neural network-based background noise removal")
.font(.caption)
.foregroundStyle(.secondary)
}


Section {
Toggle("Echo Cancellation (AEC)", isOn: $aecEnabled)
.onChange(of: aecEnabled) { _, newValue in
NotificationCenter.default.post(
name: .audioSettingsChanged,
object: ["aecEnabled": newValue]
)
}

Toggle("Noise Suppression (WebRTC)", isOn: $webrtcNsEnabled)
.onChange(of: webrtcNsEnabled) { _, newValue in
NotificationCenter.default.post(
name: .audioSettingsChanged,
object: ["webrtcNsEnabled": newValue]
)
}

Toggle("Automatic Gain Control (AGC)", isOn: $webrtcAgcEnabled)
.onChange(of: webrtcAgcEnabled) { _, newValue in
NotificationCenter.default.post(
name: .audioSettingsChanged,
object: ["webrtcAgcEnabled": newValue]
)
}

Text("WebRTC preprocessing. AEC removes speaker echo on open-mic setups; AGC normalizes input level.")
.font(.caption)
.foregroundStyle(.secondary)
} header: {
Text("Preprocessing")
}

Section {
Picker("Jitter Buffer", selection: $jitterBufferMs) {
Text("0ms (Instant)").tag(0)
Expand Down