- Introduced `Dates.swift` for handling date and time in the university's timezone. - Created `Models.swift` to define data structures for timetable, lessons, and directory search results. - Implemented `Schedule.swift` for managing lesson slots and timetable functionalities. - Developed `Storage.swift` for user settings and data persistence. - Added `Widgets.swift` to create widgets displaying lessons and upcoming classes.
58 lines
3.1 KiB
Swift
58 lines
3.1 KiB
Swift
import Foundation
|
|
import UserNotifications
|
|
|
|
/**
|
|
* Local reminders before each lesson of the coming week, from the saved timetable: they work
|
|
* offline, no push server is involved. iOS keeps at most 64 pending requests per app.
|
|
*/
|
|
enum Reminders {
|
|
private static let prefix = "lesson-"
|
|
private static let limit = 48
|
|
|
|
/** Asks for permission; false when the student declined. */
|
|
static func authorize() async -> Bool {
|
|
(try? await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge])) ?? false
|
|
}
|
|
|
|
/** Replaces every scheduled reminder with the ones the timetable needs now. */
|
|
static func reschedule(timetable: Timetable?, settings: UserSettings) {
|
|
let center = UNUserNotificationCenter.current()
|
|
Task {
|
|
let pending = await center.pendingNotificationRequests().map(\.identifier).filter { $0.hasPrefix(prefix) }
|
|
center.removePendingNotificationRequests(withIdentifiers: pending)
|
|
guard settings.reminders, let t = timetable else { return }
|
|
let now = Date()
|
|
// A week ahead, so a Friday evening already holds Monday's reminders.
|
|
let horizon = now.addingTimeInterval(7 * 24 * 3600)
|
|
var count = 0
|
|
for day in t.days {
|
|
guard let date = CivilDate(day.date), count < limit else { continue }
|
|
// One reminder per start time: parallel subgroups share the notification.
|
|
for slot in t.slots(on: date, subgroup: settings.subgroup, hidden: settings.hidden) {
|
|
let begins = date.at(slot.start)
|
|
let at = begins.addingTimeInterval(-Double(settings.reminderMinutes) * 60)
|
|
guard at > now, begins < horizon, count < limit else { continue }
|
|
let content = UNMutableNotificationContent()
|
|
content.title = slot.lessons.map(\.subject).joined(separator: " / ")
|
|
let first = slot.lessons[0]
|
|
let place = Array(Set(slot.lessons.flatMap(\.rooms))).sorted().joined(separator: ", ")
|
|
var text = slot.start.description
|
|
if let pair = first.pair { text += " · \(pair) пара" }
|
|
if !place.isEmpty { text += " · ауд. \(place)" }
|
|
if slot.lessons.contains(where: \.uncertain) { text += " · сверьте недели" }
|
|
content.body = text
|
|
content.sound = .default
|
|
content.interruptionLevel = .timeSensitive
|
|
let parts = minskCalendar.dateComponents(in: minsk, from: at)
|
|
let trigger = UNCalendarNotificationTrigger(
|
|
dateMatching: DateComponents(timeZone: minsk, year: parts.year, month: parts.month, day: parts.day, hour: parts.hour, minute: parts.minute),
|
|
repeats: false
|
|
)
|
|
try? await center.add(UNNotificationRequest(identifier: prefix + slot.id, content: content, trigger: trigger))
|
|
count += 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|