Fetch full-UK dump (no radius/limit); stations in app-group defaults only; alert path keeps focused radius fetch
This commit is contained in:
@@ -200,7 +200,7 @@ struct ContentView: View {
|
|||||||
let fetched = try await FuelPriceProvider.active.fetchStations(
|
let fetched = try await FuelPriceProvider.active.fetchStations(
|
||||||
near: location?.lat, lng: location?.lng,
|
near: location?.lat, lng: location?.lng,
|
||||||
fuel: selectedFuel,
|
fuel: selectedFuel,
|
||||||
radiusKM: Double(stationLimit) * 1.60934 // miles → km
|
radiusKM: nil // full-UK dump; device filters by miles radius
|
||||||
)
|
)
|
||||||
stations = fetched
|
stations = fetched
|
||||||
FuelStore.saveStations(fetched)
|
FuelStore.saveStations(fetched)
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import Foundation
|
|||||||
|
|
||||||
protocol FuelPriceProviding {
|
protocol FuelPriceProviding {
|
||||||
/// Fetch stations with prices. `location` may be nil (sort by price only).
|
/// Fetch stations with prices. `location` may be nil (sort by price only).
|
||||||
/// `radiusKM` bounds the search area (used by the relay). Throws on failure
|
/// `radiusKM` nil = full dataset (device filters); set = focused server-side
|
||||||
/// so callers can fall back to cached/sample data.
|
/// radius (alert path). Throws on failure so callers can fall back.
|
||||||
func fetchStations(near lat: Double?, lng: Double?, fuel: FuelType, radiusKM: Double) async throws -> [FuelStation]
|
func fetchStations(near lat: Double?, lng: Double?, fuel: FuelType, radiusKM: Double?) async throws -> [FuelStation]
|
||||||
}
|
}
|
||||||
|
|
||||||
enum FuelPriceProvider {
|
enum FuelPriceProvider {
|
||||||
@@ -32,15 +32,22 @@ enum FuelPriceProvider {
|
|||||||
struct RelayFuelProvider: FuelPriceProviding {
|
struct RelayFuelProvider: FuelPriceProviding {
|
||||||
var baseURL = URL(string: "http://192.168.1.131:8788")!
|
var baseURL = URL(string: "http://192.168.1.131:8788")!
|
||||||
|
|
||||||
func fetchStations(near lat: Double?, lng: Double?, fuel: FuelType, radiusKM: Double) async throws -> [FuelStation] {
|
func fetchStations(near lat: Double?, lng: Double?, fuel: FuelType, radiusKM: Double?) async throws -> [FuelStation] {
|
||||||
var components = URLComponents(url: baseURL.appendingPathComponent("api/v1/stations"), resolvingAgainstBaseURL: false)!
|
var components = URLComponents(url: baseURL.appendingPathComponent("api/v1/stations"), resolvingAgainstBaseURL: false)!
|
||||||
var query: [URLQueryItem] = [URLQueryItem(name: "fuel", value: fuel.rawValue)]
|
var query: [URLQueryItem] = [URLQueryItem(name: "fuel", value: fuel.rawValue)]
|
||||||
if let lat, let lng {
|
if let lat, let lng {
|
||||||
query.append(URLQueryItem(name: "lat", value: String(lat)))
|
query.append(URLQueryItem(name: "lat", value: String(lat)))
|
||||||
query.append(URLQueryItem(name: "lng", value: String(lng)))
|
query.append(URLQueryItem(name: "lng", value: String(lng)))
|
||||||
query.append(URLQueryItem(name: "radius", value: String(radiusKM)))
|
|
||||||
}
|
}
|
||||||
|
if let radiusKM {
|
||||||
|
// Focused fetch (alert path) — small response, server-side radius.
|
||||||
|
query.append(URLQueryItem(name: "radius", value: String(radiusKM)))
|
||||||
query.append(URLQueryItem(name: "limit", value: "500"))
|
query.append(URLQueryItem(name: "limit", value: "500"))
|
||||||
|
} else {
|
||||||
|
// Full-UK dump: NO radius — the device filters locally
|
||||||
|
// (5/10/15-mile radius, fuel, sort). 10,000 covers the dataset.
|
||||||
|
query.append(URLQueryItem(name: "limit", value: "10000"))
|
||||||
|
}
|
||||||
components.queryItems = query
|
components.queryItems = query
|
||||||
|
|
||||||
let (data, response) = try await URLSession.shared.data(from: components.url!)
|
let (data, response) = try await URLSession.shared.data(from: components.url!)
|
||||||
@@ -107,7 +114,7 @@ private struct RelayResponse: Codable {
|
|||||||
/// Real England-wide data comes from the relay (full-UK Fuel Finder CSV/API).
|
/// Real England-wide data comes from the relay (full-UK Fuel Finder CSV/API).
|
||||||
/// Prices in pence/litre.
|
/// Prices in pence/litre.
|
||||||
struct SampleFuelProvider: FuelPriceProviding {
|
struct SampleFuelProvider: FuelPriceProviding {
|
||||||
func fetchStations(near lat: Double?, lng: Double?, fuel: FuelType, radiusKM: Double) async throws -> [FuelStation] {
|
func fetchStations(near lat: Double?, lng: Double?, fuel: FuelType, radiusKM: Double?) async throws -> [FuelStation] {
|
||||||
try await Task.sleep(nanoseconds: 300_000_000) // simulate fetch
|
try await Task.sleep(nanoseconds: 300_000_000) // simulate fetch
|
||||||
return Self.sampleStations
|
return Self.sampleStations
|
||||||
}
|
}
|
||||||
@@ -201,7 +208,7 @@ struct FuelFinderProvider: FuelPriceProviding {
|
|||||||
let clientID: String
|
let clientID: String
|
||||||
let clientSecret: String
|
let clientSecret: String
|
||||||
|
|
||||||
func fetchStations(near lat: Double?, lng: Double?, fuel: FuelType, radiusKM: Double) async throws -> [FuelStation] {
|
func fetchStations(near lat: Double?, lng: Double?, fuel: FuelType, radiusKM: Double?) async throws -> [FuelStation] {
|
||||||
// TODO: OAuth token → GET /v1/prices → map to FuelStation.
|
// TODO: OAuth token → GET /v1/prices → map to FuelStation.
|
||||||
// The live API requires authentication; see notes above.
|
// The live API requires authentication; see notes above.
|
||||||
throw FuelProviderError.notImplemented
|
throw FuelProviderError.notImplemented
|
||||||
|
|||||||
@@ -147,15 +147,19 @@ struct FuelStore {
|
|||||||
static let lastRefreshKey = "fuelboard.lastRefresh" // TimeInterval (seconds since 1970)
|
static let lastRefreshKey = "fuelboard.lastRefresh" // TimeInterval (seconds since 1970)
|
||||||
|
|
||||||
// MARK: Stations
|
// MARK: Stations
|
||||||
|
// The full-UK dataset (~2.9 MB) lives in app-group UserDefaults only —
|
||||||
|
// keychain is for small values and cannot hold it. Read order is
|
||||||
|
// defaults-first for stations (keychain may hold a legacy small set from
|
||||||
|
// older builds; the full country dump always wins).
|
||||||
|
|
||||||
static func loadStations() -> [FuelStation] {
|
static func loadStations() -> [FuelStation] {
|
||||||
if let data = keychainData(service: stationsKey),
|
if let defaults = UserDefaults(suiteName: appGroupSuite),
|
||||||
|
let data = defaults.data(forKey: stationsKey),
|
||||||
let stations = try? JSONDecoder().decode([FuelStation].self, from: data),
|
let stations = try? JSONDecoder().decode([FuelStation].self, from: data),
|
||||||
!stations.isEmpty {
|
!stations.isEmpty {
|
||||||
return stations
|
return stations
|
||||||
}
|
}
|
||||||
if let defaults = UserDefaults(suiteName: appGroupSuite),
|
if let data = keychainData(service: stationsKey),
|
||||||
let data = defaults.data(forKey: stationsKey),
|
|
||||||
let stations = try? JSONDecoder().decode([FuelStation].self, from: data),
|
let stations = try? JSONDecoder().decode([FuelStation].self, from: data),
|
||||||
!stations.isEmpty {
|
!stations.isEmpty {
|
||||||
return stations
|
return stations
|
||||||
@@ -166,7 +170,7 @@ struct FuelStore {
|
|||||||
static func saveStations(_ stations: [FuelStation]) {
|
static func saveStations(_ stations: [FuelStation]) {
|
||||||
if let data = try? JSONEncoder().encode(stations) {
|
if let data = try? JSONEncoder().encode(stations) {
|
||||||
UserDefaults(suiteName: appGroupSuite)?.set(data, forKey: stationsKey)
|
UserDefaults(suiteName: appGroupSuite)?.set(data, forKey: stationsKey)
|
||||||
writeKeychain(data: data, service: stationsKey)
|
// Intentionally NOT written to keychain — 2.9 MB exceeds its limits.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user