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) } static func refresh() async { schedule() await AppModel().refresh() } }