diff --git a/FuelBoardWidgets/FuelPriceWidget.swift b/FuelBoardWidgets/FuelPriceWidget.swift index f89ad57..9a4bf82 100644 --- a/FuelBoardWidgets/FuelPriceWidget.swift +++ b/FuelBoardWidgets/FuelPriceWidget.swift @@ -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 {