219 lines
7.9 KiB
Swift
219 lines
7.9 KiB
Swift
import SwiftUI
|
|
|
|
// MARK: - BlackRoad Brand Colors (iOS)
|
|
|
|
extension Color {
|
|
static let brHotPink = Color(red: 1.0, green: 0.114, blue: 0.424)
|
|
static let brAmber = Color(red: 0.961, green: 0.651, blue: 0.137)
|
|
static let brElectricBlue = Color(red: 0.161, green: 0.475, blue: 1.0)
|
|
static let brViolet = Color(red: 0.612, green: 0.153, blue: 0.690)
|
|
}
|
|
|
|
// MARK: - iOS Bridge View
|
|
|
|
struct iOSContentView: View {
|
|
@EnvironmentObject var udp: BlackRoadUDPManager
|
|
|
|
var body: some View {
|
|
NavigationView {
|
|
ScrollView {
|
|
VStack(spacing: 20) {
|
|
// Connection Status Card
|
|
connectionCard
|
|
|
|
// Data Preview
|
|
if udp.isConnected {
|
|
sensorCard
|
|
aiCard
|
|
fleetCard
|
|
}
|
|
}
|
|
.padding()
|
|
}
|
|
.background(Color.black)
|
|
.navigationTitle("BlackRoad Watch")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbarColorScheme(.dark, for: .navigationBar)
|
|
.toolbarBackground(.visible, for: .navigationBar)
|
|
.toolbarBackground(Color.black, for: .navigationBar)
|
|
}
|
|
.preferredColorScheme(.dark)
|
|
}
|
|
|
|
// MARK: - Connection Card
|
|
|
|
private var connectionCard: some View {
|
|
VStack(spacing: 12) {
|
|
HStack {
|
|
Text("BLACKROAD")
|
|
.font(.system(size: 13, weight: .black, design: .monospaced))
|
|
.foregroundStyle(
|
|
LinearGradient(
|
|
colors: [.brAmber, .brHotPink],
|
|
startPoint: .leading,
|
|
endPoint: .trailing
|
|
)
|
|
)
|
|
Spacer()
|
|
Text("BRIDGE")
|
|
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
|
.foregroundColor(.gray)
|
|
}
|
|
|
|
Divider().background(Color.gray.opacity(0.3))
|
|
|
|
// M1s Connection
|
|
HStack {
|
|
Circle()
|
|
.fill(udp.isConnected ? Color.green : Color.red)
|
|
.frame(width: 10, height: 10)
|
|
Text("M1s Dock")
|
|
.font(.system(size: 14, weight: .medium))
|
|
.foregroundColor(.white)
|
|
Spacer()
|
|
if udp.isConnected {
|
|
Text(udp.sourceAddress)
|
|
.font(.system(size: 12, design: .monospaced))
|
|
.foregroundColor(.gray)
|
|
} else {
|
|
Text(udp.isListening ? "Listening on :8420" : "Starting...")
|
|
.font(.system(size: 12, design: .monospaced))
|
|
.foregroundColor(.gray)
|
|
}
|
|
}
|
|
|
|
// Apple Watch Connection
|
|
HStack {
|
|
Circle()
|
|
.fill(udp.watchReachable ? Color.green : Color.orange)
|
|
.frame(width: 10, height: 10)
|
|
Text("Apple Watch")
|
|
.font(.system(size: 14, weight: .medium))
|
|
.foregroundColor(.white)
|
|
Spacer()
|
|
Text(udp.watchReachable ? "Connected" : "Waiting")
|
|
.font(.system(size: 12, design: .monospaced))
|
|
.foregroundColor(.gray)
|
|
}
|
|
|
|
// Stats
|
|
HStack {
|
|
Label("\(udp.packetsReceived)", systemImage: "arrow.down.circle")
|
|
.font(.system(size: 12, design: .monospaced))
|
|
.foregroundColor(.brElectricBlue)
|
|
Spacer()
|
|
if let last = udp.lastUpdate {
|
|
Text(last, style: .relative)
|
|
.font(.system(size: 12))
|
|
.foregroundColor(.gray)
|
|
}
|
|
}
|
|
}
|
|
.padding()
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 16)
|
|
.fill(Color.white.opacity(0.05))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 16)
|
|
.strokeBorder(
|
|
LinearGradient(
|
|
colors: [.brAmber.opacity(0.5), .brHotPink.opacity(0.3)],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
),
|
|
lineWidth: 1
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
// MARK: - Sensor Card
|
|
|
|
private var sensorCard: some View {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
Label("SENSORS", systemImage: "thermometer")
|
|
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
|
.foregroundColor(.brAmber)
|
|
|
|
if let s = udp.sensorData {
|
|
dataRow("Temperature", String(format: "%.1f\u{00B0}C", s.temperature), .brAmber)
|
|
dataRow("Humidity", String(format: "%.1f%%", s.humidity), .brElectricBlue)
|
|
dataRow("Battery", "\(s.batteryMV)mV", s.batteryMV > 3500 ? .green : .red)
|
|
dataRow("Uptime", formatUptime(s.uptimeSec), .gray)
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding()
|
|
.background(RoundedRectangle(cornerRadius: 16).fill(Color.white.opacity(0.05)))
|
|
}
|
|
|
|
// MARK: - AI Card
|
|
|
|
private var aiCard: some View {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
Label("NPU", systemImage: "brain")
|
|
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
|
.foregroundColor(.brViolet)
|
|
|
|
if let a = udp.aiStatus {
|
|
dataRow("Load", "\(a.npuLoad)%", .brElectricBlue)
|
|
dataRow("Temp", "\(a.npuTemp)\u{00B0}C", a.npuTemp > 70 ? .red : .brAmber)
|
|
dataRow("Confidence", "\(a.confidence)%", .brHotPink)
|
|
dataRow("Inferences", "\(a.totalInferences)", .brAmber)
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding()
|
|
.background(RoundedRectangle(cornerRadius: 16).fill(Color.white.opacity(0.05)))
|
|
}
|
|
|
|
// MARK: - Fleet Card
|
|
|
|
private var fleetCard: some View {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
Label("FLEET", systemImage: "server.rack")
|
|
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
|
.foregroundColor(.brHotPink)
|
|
|
|
if let h = udp.systemHealth {
|
|
dataRow("Devices", "\(h.fleetOnline)/\(h.fleetTotal)", .brElectricBlue)
|
|
dataRow("Agents", "\(h.agentsActive)", .brViolet)
|
|
dataRow("Green", "\(h.trafficGreen)", .green)
|
|
dataRow("Tasks Done", "\(h.tasksDone)", .green)
|
|
dataRow("Repos", "\(h.reposCount)", .brAmber)
|
|
dataRow("CF Projects", "\(h.cfProjects)", .brElectricBlue)
|
|
dataRow("Memory", "\(h.memoryEntries)", .brViolet)
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding()
|
|
.background(RoundedRectangle(cornerRadius: 16).fill(Color.white.opacity(0.05)))
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
private func dataRow(_ label: String, _ value: String, _ color: Color) -> some View {
|
|
HStack {
|
|
Text(label)
|
|
.font(.system(size: 13))
|
|
.foregroundColor(.gray)
|
|
Spacer()
|
|
Text(value)
|
|
.font(.system(size: 13, weight: .semibold, design: .monospaced))
|
|
.foregroundColor(color)
|
|
}
|
|
}
|
|
|
|
private func formatUptime(_ seconds: UInt32) -> String {
|
|
let h = seconds / 3600
|
|
let m = (seconds % 3600) / 60
|
|
let s = seconds % 60
|
|
return String(format: "%02d:%02d:%02d", h, m, s)
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
iOSContentView()
|
|
.environmentObject(BlackRoadUDPManager.shared)
|
|
}
|