import SwiftUI import FlaroDesign /// Was auf dem Fernseher zu sehen ist. /// /// **Nur der Zustandswechsel, keine Navigation.** Das ist alles, was von /// `RootView` auf tvOS übrig bleibt: dort sind es 556 Zeilen Seitenleiste, /// Tab-Anpassung, gemessene Statusleistenhöhe und Mini-Player-Übergang, und /// nichts davon gibt es hier. Deshalb eine eigene Datei und kein `#if` in der /// bestehenden — ein `#if` wäre kein Zweig gewesen, sondern ein zweiter Rumpf /// in derselben Datei. struct TVRootView: View { @Environment(FlaroSession.self) private var session @Environment(PlaybackCoordinator.self) private var playback @Environment(QuickPlay.self) private var quickPlay /// Wohin ein Link aus dem Regal führt, sobald der Titel geladen ist. @State private var ziel: MediaRoute? var body: some View { ZStack { Color.flaroCanvas.ignoresSafeArea() switch session.phase { case .launching: ProgressView() case .signedOut: TVSignInView() case .connecting(let name): verbinden(mit: name) case .failed(let meldung): fehlgeschlagen(meldung) case .ready: TVShell(deepLinkTarget: $ziel) } } .task { await session.restore() } // **Hier und nicht in der Startseite.** Von einer Kachel, aus dem // Aufmacher und aus der Detailseite wird dasselbe gestartet; läge der // Übergang weiter innen, gäbe es ihn dreimal. // // `fullScreenCover(item:)` gibt es auf tvOS seit 14, und // `PlaybackRequest` ist bereits `Identifiable`. .fullScreenCover( item: Binding( get: { playback.request }, set: { if $0 == nil { playback.close() } } ) ) { request in TVPlayerScreen(request: request) } // Der Weg zurück aus dem Regal über der App. .onOpenURL { adresse in guard let link = TVDeepLink(adresse) else { return } Task { await öffne(link) } } } /// Holt den Titel und macht damit, was der Link sagt. /// /// **Nachgeschlagen, nicht geraten.** Im Regal steht nur eine Kennung; /// was dahintersteckt — Film, Serie, Folge — weiss erst der Server. Und /// bei einer Serie ist „abspielen" die nächste ungesehene Folge, was /// wiederum `QuickPlay` entscheidet. private func öffne(_ link: TVDeepLink) async { guard let backend = session.backend else { return } let kennung = switch link { case .play(let id), .detail(let id): id } guard let item = try? await backend.item(id: kennung) else { return } switch link { case .play: quickPlay.start(item, backend: backend, coordinator: playback) case .detail: ziel = MediaRoute(ratingKey: item.id, title: item.title) } } private func verbinden(mit name: String) -> some View { VStack(spacing: FlaroSpacing.regular) { ProgressView() Text(name).font(.flaroRowHeader).foregroundStyle(.white) } } private func fehlgeschlagen(_ meldung: String) -> some View { VStack(spacing: FlaroSpacing.regular) { Text(meldung).font(.flaroBody).foregroundStyle(.white) HStack(spacing: FlaroSpacing.regular) { Button("action.retry") { Task { await session.retry() } } Button("action.signOut") { session.signOut() } } } .frame(maxWidth: 900) } }