Results filter is now search radius in miles (5/10/15); relay fetch scoped to radius, limit 500; changing miles re-fetches; caption shows stations within N miles

This commit is contained in:
FuelBoard Contributor
2026-08-11 18:02:23 +01:00
parent 810cc2c006
commit 213f93aa04
4 changed files with 34 additions and 19 deletions
+12 -2
View File
@@ -39,7 +39,9 @@ struct ContentView: View {
} }
private var displayedStations: [FuelStation] { private var displayedStations: [FuelStation] {
Array(sortedStations.prefix(stationLimit)) // Relay already returns every station within the selected miles radius
// (sorted nearest-first); no local cap needed.
sortedStations
} }
private var sortedStations: [FuelStation] { private var sortedStations: [FuelStation] {
@@ -145,6 +147,10 @@ struct ContentView: View {
// No re-fetch needed one response carries E5/E10/DIESEL prices. // No re-fetch needed one response carries E5/E10/DIESEL prices.
WidgetCenter.shared.reloadAllTimelines() WidgetCenter.shared.reloadAllTimelines()
} }
.onChange(of: stationLimit) { _, _ in
// Search radius changed (miles) cached stations may not cover it.
Task { await refresh(force: true) }
}
.onChange(of: alertsEnabled) { _, newValue in .onChange(of: alertsEnabled) { _, newValue in
FuelStore.saveAlertsEnabled(newValue) FuelStore.saveAlertsEnabled(newValue)
monitor.setEnabled(newValue) monitor.setEnabled(newValue)
@@ -182,7 +188,11 @@ struct ContentView: View {
isLoading = true isLoading = true
defer { isLoading = false } defer { isLoading = false }
do { do {
let fetched = try await FuelPriceProvider.active.fetchStations(near: location?.lat, lng: location?.lng, fuel: selectedFuel) let fetched = try await FuelPriceProvider.active.fetchStations(
near: location?.lat, lng: location?.lng,
fuel: selectedFuel,
radiusKM: Double(stationLimit) * 1.60934 // miles km
)
stations = fetched stations = fetched
FuelStore.saveStations(fetched) FuelStore.saveStations(fetched)
FuelStore.saveLastRefresh() FuelStore.saveLastRefresh()
+4 -4
View File
@@ -80,9 +80,9 @@ struct StationsView: View {
if !stations.isEmpty { if !stations.isEmpty {
Divider() Divider()
Picker("Show", selection: $stationLimit) { Picker("Within", selection: $stationLimit) {
ForEach([10, 25, 50, 75, 100], id: \.self) { count in ForEach(FuelStore.stationRadiusOptions, id: \.self) { miles in
Text("\(count)").tag(count) Text("\(miles) miles").tag(miles)
} }
} }
.pickerStyle(.segmented) .pickerStyle(.segmented)
@@ -90,7 +90,7 @@ struct StationsView: View {
FuelStore.saveStationLimit(newValue) FuelStore.saveStationLimit(newValue)
} }
.padding(.vertical, 2) .padding(.vertical, 2)
Text("Showing \(stations.count) of \(totalCount) stations") Text("\(totalCount) stations within \(stationLimit) miles")
.font(.caption2) .font(.caption2)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
+8 -7
View File
@@ -13,8 +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).
/// Throws on failure so callers can fall back to cached/sample data. /// `radiusKM` bounds the search area (used by the relay). Throws on failure
func fetchStations(near lat: Double?, lng: Double?, fuel: FuelType) async throws -> [FuelStation] /// so callers can fall back to cached/sample data.
func fetchStations(near lat: Double?, lng: Double?, fuel: FuelType, radiusKM: Double) async throws -> [FuelStation]
} }
enum FuelPriceProvider { enum FuelPriceProvider {
@@ -31,15 +32,15 @@ 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) 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: "50")) query.append(URLQueryItem(name: "radius", value: String(radiusKM)))
} }
query.append(URLQueryItem(name: "limit", value: "100")) query.append(URLQueryItem(name: "limit", value: "500"))
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!)
@@ -106,7 +107,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) 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
} }
@@ -200,7 +201,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) 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
+10 -6
View File
@@ -140,7 +140,7 @@ struct FuelStore {
static let locationKey = "fuelboard.lastLocation" // "lat,lng,timestamp" static let locationKey = "fuelboard.lastLocation" // "lat,lng,timestamp"
static let fuelKey = "fuelboard.selectedFuel" // FuelType raw value static let fuelKey = "fuelboard.selectedFuel" // FuelType raw value
static let sortModeKey = "fuelboard.sortMode" // SortMode raw value static let sortModeKey = "fuelboard.sortMode" // SortMode raw value
static let stationLimitKey = "fuelboard.stationLimit" // Int (10/25/50/75/100) static let stationLimitKey = "fuelboard.stationLimitMiles" // Int miles (5/10/15)
static let favouritesKey = "fuelboard.favourites" // [FuelStation] JSON static let favouritesKey = "fuelboard.favourites" // [FuelStation] JSON
static let alertsEnabledKey = "fuelboard.alertsEnabled" // Bool static let alertsEnabledKey = "fuelboard.alertsEnabled" // Bool
static let alertsRadiusKey = "fuelboard.alertsRadius" // Double km static let alertsRadiusKey = "fuelboard.alertsRadius" // Double km
@@ -209,17 +209,21 @@ struct FuelStore {
saveString(mode.rawValue, service: sortModeKey) saveString(mode.rawValue, service: sortModeKey)
} }
// MARK: Station count limit // MARK: Station search radius (miles)
/// Search radius options the results filter shows stations within this
/// many miles of the current location.
static let stationRadiusOptions = [5, 10, 15]
static func loadStationLimit() -> Int { static func loadStationLimit() -> Int {
if let raw = loadString(service: stationLimitKey), let value = Int(raw), [10, 25, 50, 75, 100].contains(value) { if let raw = loadString(service: stationLimitKey), let value = Int(raw), stationRadiusOptions.contains(value) {
return value return value
} }
return 25 return 5
} }
static func saveStationLimit(_ limit: Int) { static func saveStationLimit(_ miles: Int) {
saveString(String(limit), service: stationLimitKey) saveString(String(miles), service: stationLimitKey)
} }
// MARK: Favourites // MARK: Favourites