74 lines
2.7 KiB
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)
|
|
}
|
|
}
|
|
}
|