Files
psu-schedule/ios/PSUSchedule/Views/SettingsView.swift

122 lines
7.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
import UserNotifications
struct SettingsView: View {
@Environment(AppModel.self) private var model
@Environment(\.dismiss) private var dismiss
@State private var changes: ScheduleChanges?
/** Whether iOS lets the app notify; nil until asked. */
@State private var notificationsAllowed: Bool?
var body: some View {
let s = model.settings
let d = s.display
NavigationStack {
Form {
Section("Тема") {
Picker("Тема", selection: Binding(get: { s.theme }, set: { model.setTheme($0) })) {
Text("Авто").tag(ThemeMode.system)
Text("Светлая").tag(ThemeMode.light)
Text("Тёмная").tag(ThemeMode.dark)
}
.pickerStyle(.segmented)
}
Section {
toggle("Цвета предметов", "Каждый предмет своим цветом в карточках, сетке и обзоре", d.colors) { var x = d; x.colors = $0; model.setDisplay(x) }
toggle("Преподаватели", "Фамилии в карточках занятий", d.teachers) { var x = d; x.teachers = $0; model.setDisplay(x) }
toggle("Аудитории", "Номера аудиторий в карточках и в сетке", d.rooms) { var x = d; x.rooms = $0; model.setDisplay(x) }
toggle("Номера пар", "«2 пара» рядом со временем", d.pairs) { var x = d; x.pairs = $0; model.setDisplay(x) }
toggle("Метки занятий", "Лекция или практика, белые и зелёные недели, общее занятие", d.tags) { var x = d; x.tags = $0; model.setDisplay(x) }
toggle("Пометки из Word", "Например, «3-13н.» — даты по ним уже рассчитаны", d.notes) { var x = d; x.notes = $0; model.setDisplay(x) }
} header: {
Text("Расписание")
} footer: {
Text("Полные сведения о занятии всегда открываются по нажатию на карточку.")
}
if !s.hidden.isEmpty {
Section("Скрытые занятия") {
ForEach(s.hidden.sorted(), id: \.self) { key in
let parts = key.split(separator: "|", maxSplits: 1).map(String.init)
HStack {
Text((parts.first ?? key).prefix(1).uppercased() + (parts.first ?? key).dropFirst() + (parts.count > 1 && !parts[1].isEmpty ? " · " + parts[1] : ""))
Spacer()
Button("Показать") { model.setHidden(key, false) }
}
}
}
}
Section {
Toggle(isOn: Binding(get: { s.reminders }, set: { on in
if on { Task { model.setReminders(await Reminders.authorize()) } } else { model.setReminders(false) }
})) {
VStack(alignment: .leading, spacing: 2) {
Text("Перед парой")
Text("Уведомление с предметом и аудиторией. Работает без интернета, по сохранённому расписанию.")
.font(.footnote).foregroundStyle(.secondary)
}
}
// On by default; the permission is asked when switched on (or for the reminders).
Toggle(isOn: Binding(get: { s.changeAlerts && notificationsAllowed != false }, set: { on in
if on {
Task {
let granted = await Reminders.authorize()
notificationsAllowed = granted
model.setChangeAlerts(granted)
}
} else { model.setChangeAlerts(false) }
})) {
VStack(alignment: .leading, spacing: 2) {
Text("Изменения в расписании")
Text(notificationsAllowed == false
? "Уведомления для приложения выключены в настройках iOS: включите их там."
: "Уведомление, когда университет перезалил документ и у группы что-то поменялось. Приходит при фоновом обновлении, когда iOS его разрешит.")
.font(.footnote).foregroundStyle(.secondary)
}
}
if s.reminders {
Picker("За сколько минут", selection: Binding(get: { s.reminderMinutes }, set: { model.setReminderMinutes($0) })) {
ForEach([5, 10, 15, 30], id: \.self) { Text("\($0)").tag($0) }
}
.pickerStyle(.segmented)
}
} header: {
Text("Напоминания")
}
Section {
LabeledContent("Документ", value: model.timetable?.document.name ?? "Группа не выбрана")
if let sem = model.timetable?.semester { LabeledContent("Семестр", value: sem.label) }
// The last republication of the document, also after its card was closed.
if let record = model.changes {
Button { changes = record } label: {
LabeledContent("Последние изменения", value: changesLabel(record.count) + " · " + CivilDate(record.at).longLabel)
}
.tint(.primary)
}
LabeledContent("Сервер", value: model.api.base.absoluteString)
} header: {
Text("О расписании")
} footer: {
Text("Расписание берётся из публичного каталога ПГУ и пересчитывается по таблице белых и зелёных недель. Приложение неофициальное: при сомнениях сверяйтесь с документом Word.")
}
}
.navigationTitle("Настройки")
.navigationBarTitleDisplayMode(.inline)
.toolbar { ToolbarItem(placement: .confirmationAction) { Button("Готово", systemImage: "checkmark") { dismiss() } } }
.sheet(item: $changes) { ChangesSheet(record: $0).presentationDetents([.medium, .large]) }
.task {
let status = await UNUserNotificationCenter.current().notificationSettings().authorizationStatus
notificationsAllowed = status == .notDetermined ? nil : status == .authorized || status == .provisional
}
}
}
private func toggle(_ title: String, _ text: String, _ on: Bool, _ set: @escaping (Bool) -> Void) -> some View {
Toggle(isOn: Binding(get: { on }, set: set)) {
VStack(alignment: .leading, spacing: 2) {
Text(title)
Text(text).font(.footnote).foregroundStyle(.secondary)
}
}
}
}