Files
psu-schedule/ios/PSUSchedule/PSUScheduleApp.swift

74 lines
2.7 KiB
Swift

import BackgroundTasks
import SwiftUI
import UIKit
@main
struct PSUScheduleApp: App {
@State private var model = AppModel()
@Environment(\.scenePhase) private var phase
var body: some Scene {
WindowGroup {
RootView()
.environment(model)
// The theme is set on the window, not with preferredColorScheme: sheets that are
// already open (the settings themselves) switch along with the screens behind them.
.onChange(of: model.settings.theme, initial: true) { old, new in Theme.apply(new, animated: old != new) }
.task { Background.schedule() }
}
.onChange(of: phase) { _, now in
// Back in the app after a while: a quiet refresh, as the Android client does on start.
if now == .active, model.settings.current != nil { Task { await model.refresh() } }
if now == .background { Background.schedule() }
}
// Every few hours iOS wakes the app to refresh the timetable, widgets and reminders.
.backgroundTask(.appRefresh(Background.identifier)) {
await Background.refresh()
}
}
}
enum Theme {
static func apply(_ mode: ThemeMode, animated: Bool) {
let style: UIUserInterfaceStyle = switch mode {
case .system: .unspecified
case .light: .light
case .dark: .dark
}
for scene in UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }) {
for window in scene.windows where window.overrideUserInterfaceStyle != style {
if animated {
UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve) {
window.overrideUserInterfaceStyle = style
}
} else {
window.overrideUserInterfaceStyle = style
}
}
}
}
}
enum Background {
static let identifier = "org.xtimms.psu.schedule.refresh"
static func schedule() {
let request = BGAppRefreshTaskRequest(identifier: identifier)
request.earliestBeginDate = Date().addingTimeInterval(6 * 3600)
try? BGTaskScheduler.shared.submit(request)
}
@MainActor
static func refresh() async {
schedule()
let model = AppModel()
let known = Set(model.allChanges.map(\.at))
await model.refresh()
// A change found by this run, for the open group: tell the student without waiting for
// them to open the app. The overview's card shows the same record.
if model.settings.changeAlerts, let record = model.changes, !record.seen, !known.contains(record.at) {
await ChangeAlerts.show(record)
}
}
}