Files
jarvis/Jarvis/JarvisApp.swift

120 lines
4.4 KiB
Swift
Raw Normal View History

// 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)
}
}