// // BleStatusView.swift // iris // // Created by Codex. // import SwiftUI struct BleStatusView: View { @EnvironmentObject private var ble: BlePeripheralManager @EnvironmentObject private var orchestrator: ContextOrchestrator var body: some View { VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 6) { Text("GlassNow BLE") .font(.title2.bold()) Text("Bluetooth: \(bluetoothStateText)") .font(.subheadline) .foregroundStyle(.secondary) } Toggle(isOn: $ble.advertisingEnabled) { VStack(alignment: .leading, spacing: 2) { Text("Advertising") .font(.headline) Text(ble.isAdvertising ? "On" : "Off") .font(.subheadline) .foregroundStyle(.secondary) } } .onChange(of: ble.advertisingEnabled) { _ in ble.start() } VStack(alignment: .leading, spacing: 8) { Text("Connection") .font(.headline) Text("Subscribed: \(ble.isSubscribed ? "Yes" : "No")") .font(.subheadline) Text("Subscribers: \(ble.subscribedCount)") .font(.subheadline) .foregroundStyle(.secondary) } VStack(alignment: .leading, spacing: 8) { Text("Telemetry") .font(.headline) Text("Last msgId: \(ble.lastMsgIdSent)") .font(.subheadline) Text("Last ping: \(ble.lastPingAt.map { timeOnly(from: $0) } ?? "Never")") .font(.subheadline) Text("Last data: \(ble.lastDataAt.map { timeOnly(from: $0) } ?? "Never")") .font(.subheadline) Text("Notify queue: \(ble.notifyQueueDepth)") .font(.subheadline) .foregroundStyle(.secondary) if ble.droppedNotifyPackets > 0 { Text("Dropped notify packets: \(ble.droppedNotifyPackets)") .font(.subheadline) .foregroundStyle(.secondary) } Text("Last notify: \(ble.lastNotifyAt.map { timeOnly(from: $0) } ?? "Never")") .font(.subheadline) .foregroundStyle(.secondary) if let cmd = ble.lastCommand, !cmd.isEmpty { Text("Last control: \(cmd)") .font(.subheadline) .foregroundStyle(.secondary) } } VStack(alignment: .leading, spacing: 12) { Button("Send Fixture Feed Now") { orchestrator.sendFixtureFeedNow() } .buttonStyle(.borderedProminent) Button("Copy UUIDs") { ble.copyUUIDsToPasteboard() } .buttonStyle(.bordered) } VStack(alignment: .leading, spacing: 8) { Text("UUIDs") .font(.headline) Text("Service: \(BlePeripheralManager.serviceUUID.uuidString)\nFEED_TX: \(BlePeripheralManager.feedTxUUID.uuidString)\nCONTROL_RX: \(BlePeripheralManager.controlRxUUID.uuidString)") .font(.caption) .textSelection(.enabled) } } .padding(.vertical, 16) .padding(.horizontal, 24) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .onAppear { ble.start() } } private var bluetoothStateText: String { switch ble.bluetoothState { case .unknown: return "Unknown" case .resetting: return "Resetting" case .unsupported: return "Unsupported" case .unauthorized: return "Unauthorized" case .poweredOff: return "Powered Off" case .poweredOn: return "Powered On" @unknown default: return "Other" } } private func timeOnly(from date: Date) -> String { let formatter = DateFormatter() formatter.dateStyle = .none formatter.timeStyle = .medium return formatter.string(from: date) } } struct BleStatusView_Previews: PreviewProvider { static var previews: some View { Text("Preview unavailable (requires EnvironmentObjects).") } }