import SwiftUI extension ScheduleChanges: Identifiable { var id: String { "\(document)|\(group)|\(at.timeIntervalSince1970)" } } /** * The overview's card after the university republished the group's document: how many lessons * changed, "Показать" for the list, and a close button. Both mark the record as seen. */ struct ChangesCard: View { var record: ScheduleChanges var onShow: () -> Void var onClose: () -> Void var body: some View { HStack(alignment: .top, spacing: 14) { Image(systemName: "arrow.triangle.2.circlepath.doc.on.clipboard") .font(.body.weight(.semibold)) .foregroundStyle(Color.onBrand) .frame(width: 40, height: 40) .background(Color.brand, in: .circle) .accessibilityHidden(true) VStack(alignment: .leading, spacing: 4) { Text("Расписание обновлено · \(changesLabel(record.count))").font(.headline) Text("Для \(record.group)" + (fileDateLabel(record.to).map { " · файл от \($0)" } ?? "")) .font(.subheadline).foregroundStyle(.secondary) Button("Показать", action: onShow) .buttonStyle(.glassProminent) .padding(.top, 6) } .frame(maxWidth: .infinity, alignment: .leading) Button("Скрыть сообщение об изменениях", systemImage: "xmark", action: onClose) .labelStyle(.iconOnly) .buttonStyle(.glass) .buttonBorderShape(.circle) } .card(padding: 18) } } /// The list behind the card: changed, added and removed lessons, as in the PWA. struct ChangesSheet: View { @Environment(\.dismiss) private var dismiss var record: ScheduleChanges var body: some View { NavigationStack { List { Section { Text("\(record.group) · \(record.name.replacingOccurrences(of: #"\.docx?$"#, with: "", options: [.regularExpression, .caseInsensitive]))" + (fileDateLabel(record.to).map { " · файл от \($0)" } ?? "") + ". Сравнение с прошлой версией, сохранённой на этом устройстве.") .font(.subheadline).foregroundStyle(.secondary) } if !record.changed.isEmpty { Section("Изменено · \(record.changed.count)") { ForEach(record.changed, id: \.self) { change in VStack(alignment: .leading, spacing: 4) { when(change.after) Text(change.after.lesson.subject).font(.headline) ForEach(change.fields, id: \.self) { f in (Text("\(f.label): ").foregroundStyle(.secondary) + Text(f.from).strikethrough().foregroundStyle(.secondary) + Text(" → ").foregroundStyle(.secondary) + Text(f.to)) .font(.subheadline) .accessibilityLabel("\(f.label): было \(f.from), стало \(f.to)") } if change.fields.isEmpty { Text("Изменился текст ячейки Word").font(.subheadline).foregroundStyle(.secondary) } } .padding(.vertical, 2) } } } if !record.added.isEmpty { Section("Добавлено · \(record.added.count)") { ForEach(record.added, id: \.self) { row in item(row, details: [row.lesson.type, row.lesson.teachers.joined(separator: ", "), withPlace(row.lesson.rooms.map { "ауд. \($0)" }.joined(separator: ", "), row.lesson.place) ?? "", row.lesson.condition]) } } } if !record.removed.isEmpty { Section("Удалено · \(record.removed.count)") { ForEach(record.removed, id: \.self) { row in item(row, removed: true, details: [row.lesson.type, row.lesson.condition]) } } } Section {} footer: { Text("Сверяйтесь с документом Word: при сомнениях он главнее.") } } .navigationTitle("Изменения") .toolbar { ToolbarItem(placement: .confirmationAction) { Button("Готово", systemImage: "checkmark") { dismiss() } } } } } private func when(_ row: ChangedLesson) -> some View { let day = row.date.flatMap { CivilDate($0) }.map { "\($0.shortWeekday), \($0.longLabel)" } ?? dayName(row.day) return Text(day + " · " + row.lesson.start).font(.subheadline.weight(.semibold)).foregroundStyle(Color.brand) } private func item(_ row: ChangedLesson, removed: Bool = false, details: [String]) -> some View { VStack(alignment: .leading, spacing: 4) { when(row).foregroundStyle(removed ? .secondary : Color.brand) Text(row.lesson.subject).font(.headline).strikethrough(removed).foregroundStyle(removed ? .secondary : .primary) let text = details.filter { !$0.trimmed.isEmpty }.joined(separator: " · ") if !text.isEmpty { Text(text).font(.subheadline).foregroundStyle(.secondary) } } .padding(.vertical, 2) } }