import Foundation /// Persists the inbox PR list to disk so the landing screen can render /// instantly from the last load, then refresh in the background. struct InboxCache { private let fileURL: URL init() { let dir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] fileURL = dir.appendingPathComponent("inbox.json") } func load() -> [PRRow] { guard let data = try? Data(contentsOf: fileURL) else { return [] } return (try? Self.decoder.decode([PRRow].self, from: data)) ?? [] } func save(_ rows: [PRRow]) { guard let data = try? JSONEncoder().encode(rows) else { return } try? data.write(to: fileURL) } /// Same ISO8601-with-offset date strategy the API decoder uses, so the /// cached `Date?` fields decode correctly. private static let decoder: JSONDecoder = { let d = JSONDecoder() let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] d.dateDecodingStrategy = .custom { decoder in let container = try decoder.singleValueContainer() let str = try container.decode(String.self) if let date = formatter.date(from: str) { return date } let plain = ISO8601DateFormatter() plain.formatOptions = [.withInternetDateTime] if let date = plain.date(from: str) { return date } throw DecodingError.dataCorruptedError( in: container, debugDescription: "Cannot decode date string \(str)") } return d }() }