Zum Inhalt

apple/PhysarumApp/Sources/TV/TVRootView.swift

#if os(tvOS)
import SwiftUI
import PhysarumEngine

/// Scene root for tvOS: owns the model (`AppState`) and the playback brain
/// (`AmbientController`), and suppresses the idle/Aerial screensaver so the ambient
/// visual keeps running.
struct TVRootView: View {
    @State private var appState: AppState
    @State private var controller: AmbientController
    // Advertises `_physarum._tcp` and accepts a phone editor-remote (T2a). Disconnect → autonomous T1.
    @State private var listener: AmbientListener?
    @State private var errorPresenter = ErrorPresenter()

    init() {
        let state = AppState(slotStore: AppState.makeProductionSlotStore())
        _appState = State(initialValue: state)
        _controller = State(initialValue: AmbientController(appState: state))
    }

    var body: some View {
        // NB: only the MetalView ignores the safe area (full-bleed render); the HUD/Hint
        // overlays inside TVPlayerView must respect the tvOS title-safe area so they don't
        // land in the overscan band on TVs that crop (e.g. an Ambilight set).
        TVPlayerView(appState: appState, controller: controller)
            .onAppear {
                UIApplication.shared.isIdleTimerDisabled = true
                appState.migrateLegacyCustomsIfNeeded()   // once: carry pre-slot customs into slots
                listener?.stop()                 // idempotent: a re-fired onAppear must not leak a 2nd listener
                let listener = AmbientListener(controller: controller)
                listener.start()
                self.listener = listener
            }
            .onDisappear {
                listener?.stop()
                listener = nil
            }
            .environment(errorPresenter)
            .errorAlert(errorPresenter)
    }
}
#endif