106 lines
4.1 KiB
Swift
106 lines
4.1 KiB
Swift
//
|
|
// Luminate.swift
|
|
//
|
|
// Copyright 2026 Brendan Szymanski <hello@bscubed.dev>
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
|
|
import Foundation
|
|
import Logging
|
|
import LuminateCore
|
|
import LuminateOnboarding
|
|
import LuminateServices
|
|
import LuminateStore
|
|
import LuminateUI
|
|
import Portico
|
|
|
|
private let logger = Logger(label: "\(AppInfo.identifier).launch")
|
|
|
|
/// The Luminate application entry point.
|
|
///
|
|
/// Owns the process-wide Jellyfin client and publishes it to the whole view tree; preferences
|
|
/// install themselves through `Preferences.bootstrap(_:)`. A stored access token is proved against
|
|
/// the server synchronously at launch, before any window exists, so the process's first window is
|
|
/// already the right one.
|
|
@main
|
|
struct Luminate: App {
|
|
var applicationId: String? { AppInfo.identifier }
|
|
|
|
private let jellyfinClient: JellyfinClient
|
|
private let sessionBinder: ClientSessionBinder
|
|
private let launchSession: LaunchSession
|
|
|
|
init() {
|
|
LogBootstrap.bootstrap()
|
|
Preferences.bootstrap(PreferenceBackend.open())
|
|
let client = JellyfinClient(
|
|
configuration: JellyfinClientConfiguration(
|
|
serverURL: Preferences.shared[.serverURL],
|
|
accessToken: Preferences.shared[.accessToken]
|
|
)
|
|
)
|
|
jellyfinClient = client
|
|
sessionBinder = ClientSessionBinder(client: client)
|
|
|
|
// Proves a stored token against the server, if there is one, before PorticoRuntime creates
|
|
// any window: blocks this thread synchronously so the process's first window is already
|
|
// the right one and no launch-time window is ever shown just to be replaced. Reconnecting
|
|
// before every check (not just retries) means a check never reuses a transport that
|
|
// previously failed to resolve DNS - see JellyfinClient.reconnect().
|
|
launchSession = LaunchSession.launch {
|
|
await client.reconnect()
|
|
_ = try await client.currentUser()
|
|
}
|
|
}
|
|
|
|
var body: some Scene {
|
|
// `launchSession.phase` is never `.verifying` on this first evaluation: the probe already
|
|
// ran, synchronously, inside `init()`. This branch is only ever taken for `.unreachable`.
|
|
if launchSession.phase != .resolved {
|
|
ApplicationWindow { _ in
|
|
logger.debug("Presenting the error window (phase=\(launchSession.phase))")
|
|
return ErrorWindow(launchSession)
|
|
}
|
|
.defaultSize(width: 800, height: 600)
|
|
} else if Preferences.shared.isAuthenticated {
|
|
ApplicationWindow { _ in
|
|
logger.debug("Presenting the main window")
|
|
return RootView()
|
|
.environment(\.client, jellyfinClient)
|
|
}
|
|
} else {
|
|
ApplicationWindow { _ in
|
|
logger.debug("Presenting the onboarding window")
|
|
return OnboardWindow(startingAt: Preferences.shared[.serverURL] == nil ? [] : [.setup])
|
|
.environment(\.client, jellyfinClient)
|
|
}
|
|
.defaultSize(width: 800, height: 600)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The root of the window's view tree.
|
|
///
|
|
/// Reads the injected client and a preference so a launch proves both resolve end to end.
|
|
private struct RootView: View {
|
|
@Environment(\.client) private var jellyfinClient
|
|
@Preference(.serverURL) private var serverURL: URL?
|
|
|
|
var body: some View {
|
|
Label { serverURL?.absoluteString ?? "No server configured" }
|
|
}
|
|
}
|