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/DailySummaryNotifications.swift

04b2b1d914788057bcd20945b876847386eb8f65
moonrise/App/Sources/DailySummaryNotifications.swift history · blame · raw

44 lines · 2309 bytes

3 symbols in this file
 1import SkyEngine
 2import SwiftUI
 3import UserNotifications
 4
 5/// Local notifications for the daily summary. The next seven are worked out on the device and
 6/// replaced each time the app becomes active or the location or setting changes.
 7enum DailySummaryNotifications {
 8    static let ids = (0..<7).map { "daily-summary-\($0)" }
 9    static let defaultMinutes = 18 * 60
10
11    @MainActor
12    static func reschedule(_ model: SkyModel) async {
13        let center = UNUserNotificationCenter.current()
14        center.removePendingNotificationRequests(withIdentifiers: ids)
15        let defaults = UserDefaults.standard
16        guard defaults.bool(forKey: SettingsKey.dailySummary) else { return }
17        await model.refreshPasses()
18        let observer = model.observer, passes = model.passes
19        let minutes = defaults.object(forKey: SettingsKey.dailySummaryMinutes) as? Int ?? defaultMinutes
20        let planned = await Task.detached { plan(observer: observer, passes: passes, minutes: minutes) }.value
21        let calendar = Calendar.current
22        for (id, fire, summary) in planned {
23            let content = UNMutableNotificationContent()
24            content.title = summary.title
25            content.body = summary.body
26            let parts = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: fire)
27            try? await center.add(UNNotificationRequest(identifier: id, content: content, trigger: UNCalendarNotificationTrigger(dateMatching: parts, repeats: false)))
28        }
29    }
30
31    nonisolated static func plan(observer: Observer, passes: [SatellitePass], minutes: Int, now: Date = .now) -> [(String, Date, DailySummary)] {
32        let calendar = Calendar.current
33        let today = calendar.startOfDay(for: now)
34        let events = SkyEngine.events(from: today, days: 8, observer: observer)
35        return ids.enumerated().compactMap { i, id in
36            guard let day = calendar.date(byAdding: .day, value: i, to: today),
37                  let fire = calendar.date(bySettingHour: minutes / 60, minute: minutes % 60, second: 0, of: day),
38                  fire > now,
39                  let summary = try? DailySummary.make(at: fire, observer: observer, passes: passes, events: events)
40            else { return nil }
41            return (id, fire, summary)
42        }
43    }
44}