Notification body/action taps route through StationTapAction: Open map opens Apple Maps (existing); More info presents the in-app detail sheet (falls back to the map sheet when the station is not in cache). Live Activity Lock Screen card and island station row pick their Link destination the same way: Maps URL for Open map, fuelboard://station deep link for More info, handled in FuelBoardApp and observed by ContentView. Also adds the small non-interactive map preview above the Directions button in the More info sheet.
119 lines
4.2 KiB
Swift
119 lines
4.2 KiB
Swift
import Foundation
|
|
import WatchConnectivity
|
|
|
|
extension Notification.Name {
|
|
static let fuelBoardWatchRefreshRequested = Notification.Name("FuelBoardWatchRefreshRequested")
|
|
static let fuelBoardShowStationDetail = Notification.Name("FuelBoardShowStationDetail")
|
|
}
|
|
|
|
@MainActor
|
|
final class WatchSyncManager: NSObject, WCSessionDelegate {
|
|
static let shared = WatchSyncManager()
|
|
|
|
private var pendingSnapshotPush = false
|
|
|
|
private override init() {
|
|
super.init()
|
|
}
|
|
|
|
func activate() {
|
|
guard WCSession.isSupported() else { return }
|
|
pendingSnapshotPush = true
|
|
let session = WCSession.default
|
|
if session.delegate !== self {
|
|
session.delegate = self
|
|
}
|
|
session.activate()
|
|
}
|
|
|
|
func pushSnapshot() {
|
|
guard WCSession.isSupported() else { return }
|
|
let session = WCSession.default
|
|
guard session.activationState == .activated else {
|
|
pendingSnapshotPush = true
|
|
return
|
|
}
|
|
do {
|
|
try session.updateApplicationContext(snapshotContext())
|
|
pendingSnapshotPush = false
|
|
} catch {
|
|
print("WATCH-SYNC push failed: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
private func snapshotContext() -> [String: Any] {
|
|
var context: [String: Any] = [:]
|
|
|
|
if let favourites = try? JSONEncoder().encode(FuelStore.loadFavourites()) {
|
|
context[FuelStore.favouritesKey] = favourites
|
|
}
|
|
// Do NOT send the full station dump to watchOS via WatchConnectivity.
|
|
// The UK dataset is megabytes large and exceeds application-context
|
|
// payload limits, which prevents any snapshot from arriving. The
|
|
// favourites payload already carries station snapshots + prices, so the
|
|
// watch can render favourites without the full list.
|
|
|
|
context[FuelStore.fuelKey] = FuelStore.loadSelectedFuel().rawValue
|
|
context[FuelStore.distanceUnitKey] = FuelStore.loadDistanceUnit().rawValue
|
|
context[FuelStore.priceDisplayStyleKey] = FuelStore.loadPriceDisplayStyle().rawValue
|
|
|
|
if let lastRefresh = FuelStore.loadLastRefresh() {
|
|
context[FuelStore.lastRefreshKey] = String(lastRefresh.timeIntervalSince1970)
|
|
}
|
|
|
|
if let handled = FuelStore.loadWatchRefreshHandled() {
|
|
context[FuelStore.watchRefreshHandledKey] = String(handled.timeIntervalSince1970)
|
|
}
|
|
|
|
if let location = FuelStore.loadLocationWithDate() {
|
|
context[FuelStore.locationKey] = "\(location.coordinate.lat),\(location.coordinate.lng),\(location.date.timeIntervalSince1970)"
|
|
}
|
|
|
|
return context
|
|
}
|
|
|
|
nonisolated func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: (any Error)?) {
|
|
if let error {
|
|
print("WATCH-SYNC activation failed: \(error.localizedDescription)")
|
|
return
|
|
}
|
|
Task { @MainActor in
|
|
if activationState == .activated, self.pendingSnapshotPush {
|
|
self.pushSnapshot()
|
|
}
|
|
}
|
|
}
|
|
|
|
nonisolated func sessionDidBecomeInactive(_ session: WCSession) {}
|
|
|
|
nonisolated func sessionDidDeactivate(_ session: WCSession) {
|
|
Task { @MainActor in
|
|
WCSession.default.activate()
|
|
}
|
|
}
|
|
|
|
nonisolated func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
|
|
handleIncomingMessage(userInfo)
|
|
}
|
|
|
|
nonisolated func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
|
|
handleIncomingMessage(message)
|
|
}
|
|
|
|
private nonisolated func handleIncomingMessage(_ payload: [String: Any]) {
|
|
if payload["fuelboard.snapshotRequest"] as? String != nil {
|
|
Task { @MainActor in
|
|
self.pushSnapshot()
|
|
}
|
|
}
|
|
|
|
guard let raw = payload[FuelStore.watchRefreshRequestKey] as? String,
|
|
let timestamp = TimeInterval(raw) else { return }
|
|
let date = Date(timeIntervalSince1970: timestamp)
|
|
Task { @MainActor in
|
|
FuelStore.requestWatchRefresh(date)
|
|
NotificationCenter.default.post(name: .fuelBoardWatchRefreshRequested, object: nil)
|
|
}
|
|
}
|
|
}
|