Widget fetches its own data from the relay
The widget previously read stations only from the app-group cache, which SideStore free accounts don't provision — the widget process saw empty data and silently fell back to sample stations (stuck on Morrisons). Now the widget makes its own focused relay fetch around its location (radius + limit 500, 5s timeout) whenever it has a fix, falling back to the shared cache then samples. Widget data now reflects the relay independently of the app. 35 tests pass.
This commit is contained in:
@@ -101,13 +101,22 @@ struct FuelPriceTimelineProvider: AppIntentTimelineProvider {
|
||||
FuelStore.saveLocation(lat: location.lat, lng: location.lng)
|
||||
}
|
||||
|
||||
// 3) Load stations, then sort per this widget's config, scoped to
|
||||
// the chosen search radius so the widget's "cheapest" matches
|
||||
// the app's list.
|
||||
var stations = FuelStore.loadStations()
|
||||
// 3) Load stations. The widget prefers its OWN focused fetch from the
|
||||
// relay so it shows real prices even when the app-group cache isn't
|
||||
// shared (SideStore free accounts don't provision the group, which
|
||||
// previously left the widget stuck on sample data). Falls back to the
|
||||
// shared cache, then to samples.
|
||||
var stations: [FuelStation] = []
|
||||
if let location {
|
||||
let radiusKM = unitToKM()
|
||||
if let fetched = await Self.fetchFocused(near: location, fuel: fuel, radiusKM: radiusKM) {
|
||||
stations = fetched
|
||||
}
|
||||
}
|
||||
if stations.isEmpty { stations = FuelStore.loadStations() }
|
||||
if stations.isEmpty { stations = SampleFuelProvider.sampleStations }
|
||||
let unit = FuelStore.loadDistanceUnit()
|
||||
let radiusKM = unit.toKM(Double(FuelStore.loadStationLimit())) // chosen units → km
|
||||
let radiusKM = unitToKM()
|
||||
if let location {
|
||||
// STRICT: cached data fetched around another location must never
|
||||
// leak out-of-radius stations into the widget.
|
||||
@@ -149,6 +158,37 @@ struct FuelPriceTimelineProvider: AppIntentTimelineProvider {
|
||||
unit: FuelStore.loadDistanceUnit()
|
||||
)
|
||||
}
|
||||
|
||||
/// The search radius in km for the widget's own focused relay fetch.
|
||||
private func unitToKM() -> Double {
|
||||
FuelStore.loadDistanceUnit().toKM(Double(FuelStore.loadStationLimit()))
|
||||
}
|
||||
|
||||
/// One focused fetch from the relay around the widget's location — small
|
||||
/// response (radius + limit 500), bounded so a dead relay can't stall the
|
||||
/// timeline. Returns nil on any failure so callers fall back to cache.
|
||||
private static func fetchFocused(near location: Coordinate, fuel: FuelType, radiusKM: Double) async -> [FuelStation]? {
|
||||
var components = URLComponents(
|
||||
url: RelayFuelProvider().baseURL.appendingPathComponent("api/v1/stations"),
|
||||
resolvingAgainstBaseURL: false
|
||||
)!
|
||||
components.queryItems = [
|
||||
URLQueryItem(name: "fuel", value: fuel.rawValue),
|
||||
URLQueryItem(name: "lat", value: String(location.lat)),
|
||||
URLQueryItem(name: "lng", value: String(location.lng)),
|
||||
URLQueryItem(name: "radius", value: String(radiusKM)),
|
||||
URLQueryItem(name: "limit", value: "500"),
|
||||
]
|
||||
var request = URLRequest(url: components.url!)
|
||||
request.timeoutInterval = 5
|
||||
do {
|
||||
let (data, response) = try await URLSession.shared.data(for: request)
|
||||
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { return nil }
|
||||
return try? FuelPriceProvider.decodeStations(from: data)
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FuelPriceWidgetView: View {
|
||||
|
||||
Reference in New Issue
Block a user