added base homescreen-widget setup, working on Android, iOS in progress
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import Foundation
|
||||
|
||||
/// Mirrors lib/widget_data/widget_data.dart. JSON keys must stay in sync —
|
||||
/// the bridge is one-way: Dart writes, Swift reads.
|
||||
enum WidgetLessonStatus: String, Codable {
|
||||
case regular
|
||||
case ongoing
|
||||
case past
|
||||
case cancelled
|
||||
case irregular
|
||||
case teacherChanged
|
||||
case event
|
||||
}
|
||||
|
||||
struct WidgetLesson: Codable {
|
||||
let start: Date
|
||||
let end: Date
|
||||
let subjectShort: String
|
||||
let subjectLong: String?
|
||||
let room: String?
|
||||
let teacher: String?
|
||||
let originalTeacher: String?
|
||||
let status: WidgetLessonStatus
|
||||
let customColor: String?
|
||||
let siblingCount: Int?
|
||||
}
|
||||
|
||||
struct WidgetPeriod: Codable {
|
||||
let name: String
|
||||
let startMinutes: Int
|
||||
let endMinutes: Int
|
||||
let virtualStartMinutes: Int
|
||||
let virtualEndMinutes: Int
|
||||
}
|
||||
|
||||
struct WidgetTimetableData: Codable {
|
||||
let fetchedAt: Date
|
||||
let anchorDate: Date
|
||||
let lessons: [WidgetLesson]
|
||||
let periods: [WidgetPeriod]
|
||||
let isHoliday: Bool
|
||||
let holidayName: String?
|
||||
}
|
||||
|
||||
enum WidgetDataKey {
|
||||
static let appGroupId = "group.eu.mhsl.marianum.mobile.client.widget"
|
||||
static let dayData = "widget_data_day_v1"
|
||||
static let weekData = "widget_data_week_v1"
|
||||
static let loggedIn = "widget_data_logged_in_v1"
|
||||
static let themeMode = "widget_setting_theme_mode_v1"
|
||||
}
|
||||
|
||||
enum WidgetDataLoader {
|
||||
/// Dart's `DateTime.toIso8601String()` on a non-UTC DateTime drops the
|
||||
/// trailing Z and ships local wall-clock time. ISO8601DateFormatter's
|
||||
/// default treats that as UTC and shifts every lesson by the local TZ
|
||||
/// offset — dispatch by suffix instead, mirroring WidgetDataParser.kt.
|
||||
private static func parseDartDate(_ raw: String) -> Date? {
|
||||
let hasTzSuffix = raw.hasSuffix("Z")
|
||||
|| raw.range(of: #"[+-]\d{2}:?\d{2}$"#, options: .regularExpression) != nil
|
||||
if hasTzSuffix {
|
||||
let iso = ISO8601DateFormatter()
|
||||
iso.formatOptions = [.withFullDate, .withFullTime, .withFractionalSeconds]
|
||||
if let d = iso.date(from: raw) { return d }
|
||||
iso.formatOptions = [.withFullDate, .withFullTime]
|
||||
return iso.date(from: raw)
|
||||
}
|
||||
for pattern in [
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSSSS",
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSS",
|
||||
"yyyy-MM-dd'T'HH:mm:ss",
|
||||
] {
|
||||
let f = DateFormatter()
|
||||
f.dateFormat = pattern
|
||||
f.timeZone = TimeZone.current
|
||||
f.locale = Locale(identifier: "en_US_POSIX")
|
||||
if let d = f.date(from: raw) { return d }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private static func decoder() -> JSONDecoder {
|
||||
let dec = JSONDecoder()
|
||||
dec.dateDecodingStrategy = .custom { decoder in
|
||||
let container = try decoder.singleValueContainer()
|
||||
let raw = try container.decode(String.self)
|
||||
if let d = parseDartDate(raw) { return d }
|
||||
throw DecodingError.dataCorruptedError(
|
||||
in: container,
|
||||
debugDescription: "Unparseable date: \(raw)"
|
||||
)
|
||||
}
|
||||
return dec
|
||||
}
|
||||
|
||||
static func loadDay() -> WidgetTimetableData? {
|
||||
load(key: WidgetDataKey.dayData)
|
||||
}
|
||||
|
||||
static func loadWeek() -> WidgetTimetableData? {
|
||||
load(key: WidgetDataKey.weekData)
|
||||
}
|
||||
|
||||
static func isLoggedIn() -> Bool {
|
||||
let defaults = UserDefaults(suiteName: WidgetDataKey.appGroupId)
|
||||
return defaults?.bool(forKey: WidgetDataKey.loggedIn) ?? false
|
||||
}
|
||||
|
||||
/// "light" / "dark" / "system". The view's `.environment(\.colorScheme)`
|
||||
/// reads this so the App's theme choice wins over the OS-level setting.
|
||||
static func themeMode() -> String {
|
||||
let defaults = UserDefaults(suiteName: WidgetDataKey.appGroupId)
|
||||
return defaults?.string(forKey: WidgetDataKey.themeMode) ?? "system"
|
||||
}
|
||||
|
||||
private static func load(key: String) -> WidgetTimetableData? {
|
||||
guard let defaults = UserDefaults(suiteName: WidgetDataKey.appGroupId),
|
||||
let raw = defaults.string(forKey: key),
|
||||
let data = raw.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
do {
|
||||
return try decoder().decode(WidgetTimetableData.self, from: data)
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user