// // 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("Feed") { if let feed = orchestrator.lastFeed, let winner = feed.winnerItem() { Text(winner.title) .font(.headline) if !winner.subtitle.isEmpty { Text(winner.subtitle) .font(.subheadline) .foregroundStyle(.secondary) } Text("type \(winner.type.rawValue) • prio \(String(format: "%.2f", winner.priority)) • ttl \(winner.ttlSec)s") .font(.caption) .foregroundStyle(.secondary) if feed.feed.count > 1 { Divider() } ForEach(feed.feed, id: \.id) { item in VStack(alignment: .leading, spacing: 6) { HStack { Text(item.title) .font(.headline) .lineLimit(1) Spacer() Text(item.type.rawValue) .font(.caption) .foregroundStyle(.secondary) } if !item.subtitle.isEmpty { Text(item.subtitle) .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(1) } Text("bucket \(item.bucket.rawValue) • prio \(String(format: "%.2f", item.priority)) • ttl \(item.ttlSec)s") .font(.caption) .foregroundStyle(.secondary) } .padding(.vertical, 4) } } else { Text("No feed 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) } } 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) } } } } if !orchestrator.lastCalendarDiagnostics.isEmpty { Section("Calendar Diagnostics") { ForEach(orchestrator.lastCalendarDiagnostics.keys.sorted(), id: \.self) { key in LabeledContent(key) { Text(orchestrator.lastCalendarDiagnostics[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) } }