feat: implement changes tracking for university documents
- Added a new data structure `ScheduleChanges` to represent changes when a university document is republished. - Implemented methods to read and write changes to a local JSON file. - Enhanced the `Repository` class to detect changes between the current and fresh timetable versions. - Updated the `MainViewModel` to expose changes to the UI and handle marking changes as seen. - Created UI components for displaying changes in both Home and Settings screens. - Added support for changes in iOS views, including a new `ChangesCard` and `ChangesSheet`. - Implemented logic to compare timetable versions and identify added, removed, and changed lessons.
This commit is contained in:
@@ -24,6 +24,8 @@ final class AppModel {
|
||||
private(set) var exams: ExamsResponse?
|
||||
private(set) var allNotes: [Note]
|
||||
private(set) var allMarks: [Mark]
|
||||
/** What changed when the university republished a document, per document and group. */
|
||||
private(set) var allChanges: [ScheduleChanges]
|
||||
private(set) var status = Status()
|
||||
/** Moves every half minute while the app is open: the clock "Идёт · ещё 20 мин" counts from. */
|
||||
private(set) var now = Date()
|
||||
@@ -39,6 +41,7 @@ final class AppModel {
|
||||
exams = Storage.read(ExamsResponse.self, "exams.json")
|
||||
allNotes = Storage.read([Note].self, "notes.json") ?? []
|
||||
allMarks = Storage.read([Mark].self, "attendance.json") ?? []
|
||||
allChanges = Storage.read([ScheduleChanges].self, "changes.json") ?? []
|
||||
Task { [weak self] in
|
||||
while !Task.isCancelled {
|
||||
try? await Task.sleep(for: .seconds(30))
|
||||
@@ -62,6 +65,21 @@ final class AppModel {
|
||||
t.slots(on: date, subgroup: subgroup, hidden: hidden)
|
||||
}
|
||||
|
||||
/** The open group's last republished document: what changed for it. */
|
||||
var changes: ScheduleChanges? {
|
||||
guard let t = timetable else { return nil }
|
||||
return allChanges.first { $0.document == t.document.id && $0.group == t.group }
|
||||
}
|
||||
|
||||
/** The overview's card was opened or closed: the record stays for "Последние изменения". */
|
||||
func markChangesSeen(_ record: ScheduleChanges) {
|
||||
guard !record.seen else { return }
|
||||
for i in allChanges.indices where allChanges[i].document == record.document && allChanges[i].group == record.group {
|
||||
allChanges[i].seen = true
|
||||
}
|
||||
Storage.write(allChanges, "changes.json")
|
||||
}
|
||||
|
||||
/** Upcoming exams of the open group, soonest first; the cache of another group is ignored. */
|
||||
var upcomingExams: [Exam] {
|
||||
guard let t = timetable, let e = exams, e.group == t.group else { return [] }
|
||||
@@ -110,6 +128,11 @@ final class AppModel {
|
||||
status.loading = true
|
||||
do {
|
||||
let fresh = try await block()
|
||||
// Also in a background refresh: the card waits for the student on the overview.
|
||||
if let record = ScheduleChanges.between(timetable, fresh) {
|
||||
allChanges = Array(([record] + allChanges.filter { $0.document != record.document || $0.group != record.group }).prefix(12))
|
||||
Storage.write(allChanges, "changes.json")
|
||||
}
|
||||
timetable = fresh
|
||||
Storage.write(fresh, "timetable.json")
|
||||
lastFailed = nil
|
||||
|
||||
Reference in New Issue
Block a user