tv-anarchy/Sources/TVAnarchyiOS/TVAnarchyiOSApp.swift
Natalie 4a2ceb9781 feat(offline): inline star-to-keep and trash-to-cull on cache rows
Surface the existing pin (keep-from-cull) and per-file delete actions as
visible inline buttons on each offline cache row instead of context-menu-only:
a star toggles protection from auto-cull (and restore-if-missing), a trash
culls that file early. Aligns wording/icons to the star metaphor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:12:41 -04:00

81 lines
3.3 KiB
Swift

// 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") }
}
}
}