// // Luminate.swift // // Copyright 2026 Brendan Szymanski // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later // import Adwaita import Foundation import Logging import LuminateCore import LuminateDI import LuminatePlayer import LuminateUI @main struct Luminate: App { let app = AdwaitaApp(id: "dev.bscubed.Luminate") @State private var client: JellyfinClient? @State private var userId = "" @State private var isLaunchLoading = true init() { LoggingSystem.bootstrap { label in var handler = StreamLogHandler.standardOutput(label: label) #if DEBUG handler.logLevel = .debug #else handler.logLevel = .info #endif return handler } ObservationRegistrar.onChange = { StateManager.updateViews() } if let store = try? SQLiteStore(dbURL: SQLiteStore.defaultDatabaseURL()) { DIContainer.shared.register(\.persistence, value: store) } DIContainer.shared.register(\.imageService, value: ImageService()) DIContainer.shared.register(\.viewUpdateScheduler, value: ViewUpdateScheduler()) DIContainer.shared.register(\.logger, value: Logger(label: "dev.bscubed.Luminate")) } var scene: Scene { Window(id: "main") { window in if isLaunchLoading { VStack { Spinner() } .onAppear { loadSavedSession() } } else if let client { ContentView( client: client, userId: userId ) } else { ServerSetupView { client, id in DIContainer.shared.register(\.client, value: client) DIContainer.shared.register(\.userId, value: id) self.client = client self.userId = id } } } .defaultSize(width: 1280, height: 800) .quitShortcut() .closeShortcut() .keyboardShortcut("f".ctrl()) { _ in } .keyboardShortcut("r".ctrl()) { _ in } #if DEBUG .devel() #endif } private func loadSavedSession() { Task { do { defer { isLaunchLoading = false } let store = try SQLiteStore(dbURL: SQLiteStore.defaultDatabaseURL()) let auth = try await store.loadAuth() guard let serverURL = URL(string: auth.serverURL) else { return } let client = JellyfinClient( serverURL: serverURL, token: auth.token, userId: auth.userId) let isValid = await client.validateToken() guard isValid else { return } DIContainer.shared.register(\.client, value: client) DIContainer.shared.register(\.userId, value: auth.userId) self.client = client self.userId = auth.userId } catch { isLaunchLoading = false } } } } struct ContentView: View { var client: JellyfinClient var userId: String @State var stack: NavigationStack = .init() var view: Body { NavigationView($stack, "Luminate") { page in switch page { case .items(let title, let items, let type): ItemPage(title: title, items: items, type: type, navigation: $stack) .topToolbar { ToolbarView() } .navigationTitle(page.description) case .item(let item, let type): ItemPage(item: item, type: type, navigation: $stack) .topToolbar { ToolbarView() } .navigationTitle(page.description) case .movieDetail(let item): MovieDetailView(for: item) .topToolbar { ToolbarView() } .navigationTitle(page.description) } } initialView: { HomeView(navigation: $stack) .topToolbar { ToolbarView() } .navigationTitle("Luminate") } } }