Location-driven results: widget fetches its own location (async, cache fallback), app auto-requests on first launch, Halifax sample stations
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
// WidgetLocationFetcher.swift — one-shot location for the WIDGET EXTENSION.
|
||||
//
|
||||
// WidgetKit providers run in a short budget, so this fetcher is deliberately
|
||||
// timeout-bounded: it requests a single location and gives up after a few
|
||||
// seconds, falling back to whatever the caller already has cached.
|
||||
//
|
||||
// Location permission is shared with the containing app (same TCC identity),
|
||||
// so if the user granted "While Using" in FuelBoard, the widget can use
|
||||
// location directly — no extra prompt in the car.
|
||||
|
||||
import Foundation
|
||||
import CoreLocation
|
||||
|
||||
@MainActor
|
||||
final class WidgetLocationFetcher: NSObject, CLLocationManagerDelegate {
|
||||
static let shared = WidgetLocationFetcher()
|
||||
|
||||
private let manager = CLLocationManager()
|
||||
private var continuation: CheckedContinuation<CLLocation?, Never>?
|
||||
private var timeoutTask: Task<Void, Never>?
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
manager.delegate = self
|
||||
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
||||
}
|
||||
|
||||
/// Requests the current location. Returns nil on denial, timeout, or
|
||||
/// no-permission — callers should fall back to cached data.
|
||||
func currentLocation(timeout: TimeInterval = 4) async -> CLLocation? {
|
||||
guard CLLocationManager.locationServicesEnabled() else { return nil }
|
||||
|
||||
switch manager.authorizationStatus {
|
||||
case .authorizedWhenInUse, .authorizedAlways:
|
||||
break
|
||||
case .notDetermined:
|
||||
// Widget extensions share the app's TCC identity; if the app was
|
||||
// never opened, the system can't prompt from here — fall back.
|
||||
return nil
|
||||
case .denied, .restricted:
|
||||
return nil
|
||||
@unknown default:
|
||||
return nil
|
||||
}
|
||||
|
||||
return await withCheckedContinuation { cont in
|
||||
continuation = cont
|
||||
manager.requestLocation()
|
||||
timeoutTask = Task { [weak self] in
|
||||
try? await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000))
|
||||
self?.continuation?.resume(returning: nil)
|
||||
self?.continuation = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||
MainActor.assumeIsolated {
|
||||
timeoutTask?.cancel()
|
||||
continuation?.resume(returning: locations.last)
|
||||
continuation = nil
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
||||
MainActor.assumeIsolated {
|
||||
timeoutTask?.cancel()
|
||||
continuation?.resume(returning: nil)
|
||||
continuation = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user