Add 'CalendarrKit/' from commit '9ea1a8705d608b1aa476c0b35685c57466bb0c50'
git-subtree-dir: CalendarrKit git-subtree-mainline:8bc32faa7fgit-subtree-split:9ea1a8705d
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import Foundation
|
||||
|
||||
/// Identifies the appointment a complication is currently showing.
|
||||
///
|
||||
/// The start date is part of the identity on purpose: a moved event keeps its
|
||||
/// id, and the complication is wrong until it is told.
|
||||
public struct NextEventIdentity: Equatable, Sendable, Codable {
|
||||
public let id: String
|
||||
public let start: Date
|
||||
|
||||
public init(id: String, start: Date) {
|
||||
self.id = id
|
||||
self.start = start
|
||||
}
|
||||
}
|
||||
|
||||
public enum WatchPushPolicy {
|
||||
/// The first event that has not ended yet — which is what every "next
|
||||
/// appointment" complication renders, including one that is running now.
|
||||
public static func nextEventIdentity(in snapshot: CalendarrSnapshot,
|
||||
at now: Date) -> NextEventIdentity? {
|
||||
snapshot.events
|
||||
.filter { $0.end > now }
|
||||
.min { $0.start < $1.start }
|
||||
.map { NextEventIdentity(id: $0.id, start: $0.start) }
|
||||
}
|
||||
|
||||
/// Whether this change is worth one of the day's limited complication
|
||||
/// pushes. Only a changed next appointment is — everything else reaches the
|
||||
/// watch on the next application-context delivery.
|
||||
public static func shouldPushComplicationUpdate(previous: NextEventIdentity?,
|
||||
current: NextEventIdentity?) -> Bool {
|
||||
previous != current
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import Foundation
|
||||
|
||||
/// Dictionary keys used on the WatchConnectivity wire. Stable within a major
|
||||
/// version, like the snapshot filenames.
|
||||
public enum WatchTransportKey {
|
||||
public static let schema = "schema"
|
||||
public static let snapshot = "snapshot"
|
||||
public static let calendars = "calendars"
|
||||
public static let session = "session"
|
||||
}
|
||||
|
||||
public enum WatchTransportError: Error, Equatable, LocalizedError {
|
||||
case missingSnapshot
|
||||
case unsupportedSchema(found: Int, maxSupported: Int)
|
||||
case malformed(String)
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .missingSnapshot:
|
||||
return "Watch payload carried no snapshot."
|
||||
case .unsupportedSchema(let found, let max):
|
||||
return "Watch payload schema \(found) is newer than this build understands (\(max))."
|
||||
case .malformed(let why):
|
||||
return "Watch payload could not be decoded: \(why)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What the phone sends to the watch: a trimmed snapshot, the calendar list for
|
||||
/// a filter UI, and the non-secret session facts so the watch can tell "signed
|
||||
/// out" from "never received anything".
|
||||
///
|
||||
/// The auth token is deliberately absent. The watch never talks to the server,
|
||||
/// so there is nothing for it to authenticate with and nothing to leak.
|
||||
public struct WatchTransportPayload: Sendable {
|
||||
/// Bump only for a breaking change to the dictionary shape.
|
||||
public static let currentSchema = 1
|
||||
/// Payloads claiming a higher schema are refused rather than mis-decoded.
|
||||
public static let maxSupportedSchema = 1
|
||||
|
||||
public let snapshot: CalendarrSnapshot
|
||||
public let calendars: [SnapshotCalendar]
|
||||
public let session: SharedSession?
|
||||
|
||||
public init(snapshot: CalendarrSnapshot,
|
||||
calendars: [SnapshotCalendar],
|
||||
session: SharedSession?) {
|
||||
self.snapshot = snapshot
|
||||
self.calendars = calendars
|
||||
self.session = session
|
||||
}
|
||||
|
||||
private static func encoder() -> JSONEncoder {
|
||||
let e = JSONEncoder(); e.dateEncodingStrategy = .iso8601; return e
|
||||
}
|
||||
|
||||
private static func decoder() -> JSONDecoder {
|
||||
let d = JSONDecoder(); d.dateDecodingStrategy = .iso8601; return d
|
||||
}
|
||||
|
||||
/// Encode for `WCSession`. Values are `Data` and `Int` only, because
|
||||
/// WatchConnectivity accepts property-list types and rejects everything
|
||||
/// else at runtime rather than at compile time.
|
||||
public func encoded() throws -> [String: Any] {
|
||||
let e = Self.encoder()
|
||||
var dict: [String: Any] = [
|
||||
WatchTransportKey.schema: Self.currentSchema,
|
||||
WatchTransportKey.snapshot: try e.encode(snapshot),
|
||||
WatchTransportKey.calendars: try e.encode(calendars),
|
||||
]
|
||||
if let session { dict[WatchTransportKey.session] = try e.encode(session) }
|
||||
return dict
|
||||
}
|
||||
|
||||
public static func decode(_ dict: [String: Any]) throws -> WatchTransportPayload {
|
||||
let found = dict[WatchTransportKey.schema] as? Int ?? 1
|
||||
guard found <= maxSupportedSchema else {
|
||||
throw WatchTransportError.unsupportedSchema(found: found, maxSupported: maxSupportedSchema)
|
||||
}
|
||||
guard let snapshotData = dict[WatchTransportKey.snapshot] as? Data else {
|
||||
throw WatchTransportError.missingSnapshot
|
||||
}
|
||||
|
||||
let d = decoder()
|
||||
let snapshot: CalendarrSnapshot
|
||||
do { snapshot = try d.decode(CalendarrSnapshot.self, from: snapshotData) }
|
||||
catch { throw WatchTransportError.malformed("snapshot: \(error)") }
|
||||
|
||||
var calendars: [SnapshotCalendar] = []
|
||||
if let data = dict[WatchTransportKey.calendars] as? Data {
|
||||
calendars = (try? d.decode([SnapshotCalendar].self, from: data)) ?? []
|
||||
}
|
||||
|
||||
var session: SharedSession?
|
||||
if let data = dict[WatchTransportKey.session] as? Data {
|
||||
session = try? d.decode(SharedSession.self, from: data)
|
||||
}
|
||||
|
||||
return WatchTransportPayload(snapshot: snapshot, calendars: calendars, session: session)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user