krz/moonrise

Night-sky companion for iPhone, Apple Watch and Vision Pro: what's up tonight, the Moon, planets, launches and missions, and a Library of the solar system. SwiftUI app with a Cloudflare Workers backend. astronomy cloudflare-workers ios night-sky space swiftui visionos watchos

App/Sources/MoonriseApp.swift

59d401dca1a327705632d1e2e20f00f807dee828
moonrise/App/Sources/MoonriseApp.swift history · blame · raw

76 lines · 3617 bytes

7 symbols in this file
 1import AppIntents
 2import SkyRenderer
 3import SwiftUI
 4import UIKit
 5import UserNotifications
 6import os
 7
 8let log = Logger(subsystem: "sh.krz.moonrise", category: "app")
 9
10@main
11struct MoonriseApp: App {
12    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
13    @AppStorage(SettingsKey.onboarded) private var onboarded = false
14    @AppStorage(SettingsKey.nightActive) private var nightActive = false
15    @State private var router = Router.shared
16    @Environment(\.scenePhase) private var scenePhase
17
18    var body: some Scene {
19        WindowGroup {
20            TabView(selection: $router.tab) {
21                Tab("Sky", systemImage: "sparkles", value: "sky") { SkyScreen() }
22                Tab("System", systemImage: "circle.circle", value: "system") { SystemScreen() }
23                Tab("Sun", systemImage: "sun.max", value: "sun") { SunScreen() }
24                Tab("News & launches", systemImage: "airplane.departure", value: "launches") { LaunchesScreen() }
25                Tab("Library", systemImage: "books.vertical", value: "library") { LibraryScreen() }
26            }
27            .tabViewStyle(.sidebarAdaptable)
28            // Red tab highlight while the Sky tab is in night mode.
29            .tint(router.tab == "sky" && nightActive ? SkyStyle.nightRed : nil)
30            .preferredColorScheme(.dark)
31            .environment(\.openURL, InAppBrowser.action)
32            .fullScreenCover(isPresented: .constant(!onboarded)) {
33                OnboardingScreen().preferredColorScheme(.dark)
34            }
35            .sheet(item: $router.libraryPage) { page in
36                NavigationStack { LibraryPageView(page: page) }
37            }
38            .onChange(of: scenePhase) { if scenePhase == .active { Task { await LaunchPassNotifications.refresh() } } }
39        }
40    }
41}
42
43final class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
44    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
45        ImageLoader.configureURLCache()
46        UNUserNotificationCenter.current().delegate = self
47        application.registerForRemoteNotifications()
48        MoonriseShortcuts.updateAppShortcutParameters()
49        Task.detached(priority: .background) { await SpotlightIndexer.indexIfNeeded() }
50        return true
51    }
52
53    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
54        let token = deviceToken.map { String(format: "%02x", $0) }.joined()
55        log.info("APNs device token: \(token, privacy: .public)")
56        Task { @MainActor in AuroraAlerts.shared.receivedToken(token) }
57    }
58
59    /// A news digest opens News & launches on the News switch.
60    nonisolated func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
61        guard response.notification.request.content.userInfo["tab"] as? String == "news" else { return }
62        await MainActor.run {
63            Router.shared.tab = "launches"
64            Router.shared.showNews = true
65        }
66    }
67
68    /// Show alerts as banners while the app is open.
69    nonisolated func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
70        [.banner, .sound]
71    }
72
73    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
74        log.error("APNs registration failed: \(error.localizedDescription, privacy: .public)")
75    }
76}