// JarvisApp.swift import SwiftUI import SwiftData import UIKit import BackgroundTasks // MARK: - Logo + splash /// The three-bar logo that mirrors the app icon. struct JarvisLogoMark: View { var width: CGFloat = 180 var body: some View { VStack(alignment: .center, spacing: 8) { // Orange (short) · White (longest) · Grey (medium) — same ratios as icon Capsule().fill(Palette.orange) .frame(width: width * 0.48, height: width * 0.075) .shadow(color: Palette.orange.opacity(0.55), radius: 10, y: 2) Capsule().fill(Color.white) .frame(width: width, height: width * 0.075) Capsule().fill(Color(hex: "888888")) .frame(width: width * 0.74, height: width * 0.075) } } } struct SplashView: View { var body: some View { ZStack { Color(hex: "0A0A0A").ignoresSafeArea() VStack(spacing: 28) { JarvisLogoMark(width: 180) JarvisWordmark(size: 34) } } } } final class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { // Briefing notifications refresh BGTaskScheduler.shared.register(forTaskWithIdentifier: jarvisBriefingRefreshID, using: nil) { task in Task { @MainActor in await NotificationManager.shared.applySettings() NotificationManager.shared.scheduleBackgroundRefresh() task.setTaskCompleted(success: true) } } // Story feed cache refresh BGTaskScheduler.shared.register(forTaskWithIdentifier: jarvisFeedRefreshID, using: nil) { task in guard let refresh = task as? BGAppRefreshTask else { task.setTaskCompleted(success: false); return } BackgroundRefreshManager.handleFeedRefresh(task: refresh) } return true } } @main struct JarvisApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate @AppStorage("appearanceMode") private var appearanceMode: AppearanceMode = .dark @StateObject private var settings = ServerSettings.shared @StateObject private var store = StoryStore.shared @StateObject private var ws = WebSocketManager.shared @StateObject private var connectivity = ConnectivitySettings.shared @StateObject private var connManager = ConnectivityManager.shared @StateObject private var notifications = NotificationManager.shared @State private var showSplash = true // Single ModelContainer instance shared with BackgroundRefreshManager. private let container: ModelContainer = { let schema = Schema([CachedStory.self, CachedArticle.self, ReadStory.self, SavedStory.self, SeenStory.self]) return try! ModelContainer(for: schema) }() var body: some Scene { WindowGroup { ZStack { if showSplash { SplashView() .transition(.opacity) } else { Group { if settings.isConfigured { RootTabView() } else { OnboardingView() } } .transition(.opacity) } } .animation(.easeOut(duration: 0.5), value: showSplash) .preferredColorScheme(appearanceMode.colorScheme) .environmentObject(settings) .environmentObject(store) .environmentObject(ws) .environmentObject(connectivity) .environmentObject(connManager) .environmentObject(notifications) .task { BackgroundRefreshManager.container = container BackgroundRefreshManager.scheduleFeedRefresh() connManager.start() await connManager.resolveAndActivate() await notifications.bootstrap() // Hold splash until setup is done, minimum 1.3 s for legibility. try? await Task.sleep(nanoseconds: 1_300_000_000) showSplash = false } } .modelContainer(container) } }