// // OrchestratorView.swift // iris // // Created by Codex. // import CoreLocation import Foundation import MusicKit import SwiftUI @available(iOS 16.0, *) struct OrchestratorView: View { @EnvironmentObject private var orchestrator: ContextOrchestrator var body: some View { NavigationStack { List { Section("Location") { LabeledContent("Auth") { Text(authText(orchestrator.authorization)) } if let loc = orchestrator.lastLocation { LabeledContent("Lat/Lon") { Text("\(format(loc.coordinate.latitude, 5)), \(format(loc.coordinate.longitude, 5))") .textSelection(.enabled) } LabeledContent("Accuracy") { Text("\(Int(loc.horizontalAccuracy)) m") } LabeledContent("Speed") { Text(speedText(loc.speed)) } } else { Text("No location yet") .foregroundStyle(.secondary) } } Section("Recompute") { LabeledContent("Last reason") { Text(orchestrator.lastRecomputeReason ?? "—") } LabeledContent("Last time") { Text(orchestrator.lastRecomputeAt.map(timeOnly) ?? "—") } LabeledContent("Elapsed") { Text(orchestrator.lastPipelineElapsedMs.map { "\($0) ms" } ?? "—") } LabeledContent("Fetch failed") { Text(orchestrator.lastFetchFailed ? "Yes" : "No") } if let err = orchestrator.lastError { Text(err) .font(.footnote) .foregroundStyle(.secondary) } Button("Recompute Now") { orchestrator.recomputeNow() } } Section("Winner") { if let env = orchestrator.lastWinner { Text(env.winner.title) .font(.headline) Text(env.winner.subtitle) .font(.subheadline) .foregroundStyle(.secondary) Text("type \(env.winner.type.rawValue) • prio \(String(format: "%.2f", env.winner.priority)) • ttl \(env.winner.ttlSec)s") .font(.caption) .foregroundStyle(.secondary) } else { Text("No winner yet") .foregroundStyle(.secondary) } } Section("Now Playing") { LabeledContent("Music auth") { Text(musicAuthText(orchestrator.musicAuthorization)) } if let snapshot = orchestrator.nowPlaying { Text(snapshot.title) .font(.headline) .lineLimit(1) Text(nowPlayingSubtitle(snapshot)) .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(1) Text(String(describing: snapshot.playbackStatus)) .font(.caption) .foregroundStyle(.secondary) } else { Text(orchestrator.musicAuthorization == .authorized ? "Nothing playing" : "Not authorized") .foregroundStyle(.secondary) } } Section("Candidates (\(orchestrator.lastCandidates.count))") { if orchestrator.lastCandidates.isEmpty { Text("No candidates") .foregroundStyle(.secondary) } else { ForEach(orchestrator.lastCandidates, id: \.id) { c in VStack(alignment: .leading, spacing: 6) { HStack { Text(c.title) .font(.headline) .lineLimit(1) Spacer() Text(c.type.rawValue) .font(.caption) .foregroundStyle(.secondary) } Text(c.subtitle) .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(1) Text("conf \(String(format: "%.2f", c.confidence)) • ttl \(c.ttlSec)s") .font(.caption) .foregroundStyle(.secondary) } .padding(.vertical, 4) } } } if !orchestrator.lastWeatherDiagnostics.isEmpty { Section("Weather Diagnostics") { ForEach(orchestrator.lastWeatherDiagnostics.keys.sorted(), id: \.self) { key in LabeledContent(key) { Text(orchestrator.lastWeatherDiagnostics[key] ?? "") .font(.caption) .foregroundStyle(.secondary) .textSelection(.enabled) } } } } Section("Test") { Button("Send Fixture Feed Now") { orchestrator.sendFixtureFeedNow() } } } .navigationTitle("Orchestrator") } } private func authText(_ s: CLAuthorizationStatus) -> String { switch s { case .notDetermined: return "Not Determined" case .restricted: return "Restricted" case .denied: return "Denied" case .authorizedAlways: return "Always" case .authorizedWhenInUse: return "When In Use" @unknown default: return "Other" } } private func timeOnly(_ date: Date) -> String { let formatter = DateFormatter() formatter.dateStyle = .none formatter.timeStyle = .medium return formatter.string(from: date) } private func speedText(_ speed: CLLocationSpeed) -> String { guard speed >= 0 else { return "—" } return "\(String(format: "%.1f", speed)) m/s" } private func musicAuthText(_ status: MusicAuthorization.Status) -> String { switch status { case .notDetermined: return "Not Determined" case .denied: return "Denied" case .restricted: return "Restricted" case .authorized: return "Authorized" @unknown default: return "Other" } } private func nowPlayingSubtitle(_ snapshot: NowPlayingSnapshot) -> String { let parts = [snapshot.artist, snapshot.album] .compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) } .filter { !$0.isEmpty } return parts.isEmpty ? "Apple Music" : parts.joined(separator: " • ") } private func format(_ value: Double, _ precision: Int) -> String { String(format: "%.\(precision)f", value) } }