// TVAnarchy iOS — a thin bridge client: browse the network library (shows + // movies), stream/play in-app via VLCKit, download for offline (background // session + prefetch-ahead + flight pack), and remote-control any registry // device. All heavy logic lives behind the tv-anarchy-bridge. import SwiftUI import LilithDesignTokens /// UIKit hook for the background download session: iOS relaunches the app when /// queued downloads finish and hands over a completion handler that must be /// called once the session's events drain (DownloadManager does the calling). final class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { guard identifier == DownloadManager.sessionIdentifier else { return } Task { @MainActor in BackgroundSessionRelay.shared.completionHandler = completionHandler } } } @main struct TVAnarchyiOSApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate @Environment(\.scenePhase) private var scenePhase @StateObject private var settings = BridgeSettings() @StateObject private var downloads = DownloadManager() /// The install join gate (Devices pillar): loading → member | needsJoin. A phone is a bridge /// client, so membership = "a bridge is configured" (FleetGate latch, internal name) — /// resolved before the tabs exist; a fresh install sees only the join page. /// See v2/pillars/devices.md (product: "Join install", "Device Mesh"). @State private var phase: FleetPhase = .loading var body: some Scene { WindowGroup { Group { switch phase { case .loading: ProgressView() .task { phase = FleetGate.isMember(settings: settings) ? .member : .needsJoin } case .member: RootTabView() case .needsJoin: JoinFleetView { phase = .member } } } .environmentObject(settings) .environmentObject(downloads) .preferredColorScheme(.dark) .tint(AppColors.primary) } .onChange(of: scenePhase) { _, newScenePhase in guard newScenePhase == .active, phase == .member else { return } Task { // Re-pick LAN vs WireGuard, then top up offline shows if enabled. await settings.probeHosts() if settings.packEnabled, let client = settings.client { await FlightPack.run(client: client, downloads: downloads, settings: settings) } } } } } struct RootTabView: View { var body: some View { TabView { LibraryView() .tabItem { Label("Library", systemImage: "play.tv") } DownloadsView() .tabItem { Label("Downloads", systemImage: "arrow.down.circle") } RemoteView() .tabItem { Label("Remote", systemImage: "appletvremote.gen4") } SettingsScreen() .tabItem { Label("Settings", systemImage: "gearshape") } } } }