Add Settings tab: distance units toggle, onboarding replay, £4.99 tip; drop E10/E5 from labels
- FuelType labels: 'Unleaded (E10)'/'Premium (E5)' -> 'Unleaded'/'Premium' - New DistanceUnit (miles/km) preference; all app + widget distances, radii and alert text convert/display in the chosen unit - Settings tab (4th tab): segmented miles/km picker, 'Show introduction' replay button (onboarding moved out of Alerts tab), StoreKit tip £4.99 - ProximityMonitor + widget notifications use the chosen unit - Tests: label expectations updated, DistanceUnit conversion + format tests
This commit is contained in:
@@ -36,15 +36,17 @@ struct FuelPriceEntry: TimelineEntry {
|
||||
let fuel: FuelType
|
||||
let location: Coordinate?
|
||||
let locationSource: String // "live" | "cached" | "none"
|
||||
let unit: DistanceUnit // user's display unit for distances
|
||||
}
|
||||
|
||||
struct FuelPriceTimelineProvider: TimelineProvider {
|
||||
func placeholder(in context: Context) -> FuelPriceEntry {
|
||||
let fuel = FuelStore.loadSelectedFuel()
|
||||
let unit = FuelStore.loadDistanceUnit()
|
||||
let sample = SampleFuelProvider.sampleStations
|
||||
.filter { $0.prices[fuel] != nil }
|
||||
.sorted { $0.prices[fuel]! < $1.prices[fuel]! }
|
||||
return FuelPriceEntry(date: Date(), stations: Array(sample.prefix(4)), fuel: fuel, location: nil, locationSource: "none")
|
||||
return FuelPriceEntry(date: Date(), stations: Array(sample.prefix(4)), fuel: fuel, location: nil, locationSource: "none", unit: unit)
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (FuelPriceEntry) -> Void) {
|
||||
@@ -85,11 +87,12 @@ struct FuelPriceTimelineProvider: TimelineProvider {
|
||||
}
|
||||
|
||||
// 3) Load stations, then sort by price (distance tiebreak), scoped to
|
||||
// the chosen search radius (miles) so the widget's "cheapest" matches
|
||||
// the chosen search radius so the widget's "cheapest" matches
|
||||
// the app's list.
|
||||
var stations = FuelStore.loadStations()
|
||||
if stations.isEmpty { stations = SampleFuelProvider.sampleStations }
|
||||
let radiusKM = Double(FuelStore.loadStationLimit()) * 1.60934 // miles → km
|
||||
let unit = FuelStore.loadDistanceUnit()
|
||||
let radiusKM = unit.toKM(Double(FuelStore.loadStationLimit())) // chosen units → km
|
||||
if let location {
|
||||
// STRICT: cached data fetched around another location must never
|
||||
// leak out-of-radius stations into the widget.
|
||||
@@ -112,7 +115,7 @@ struct FuelPriceTimelineProvider: TimelineProvider {
|
||||
sorted = filtered.sorted { $0.prices[fuel]! < $1.prices[fuel]! }
|
||||
}
|
||||
|
||||
return FuelPriceEntry(date: Date(), stations: Array(sorted.prefix(4)), fuel: fuel, location: location, locationSource: source)
|
||||
return FuelPriceEntry(date: Date(), stations: Array(sorted.prefix(4)), fuel: fuel, location: location, locationSource: source, unit: FuelStore.loadDistanceUnit())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +161,7 @@ struct FuelPriceWidgetView: View {
|
||||
.foregroundStyle(.green)
|
||||
}
|
||||
if let location = entry.location {
|
||||
Text(String(format: "%.1f mi away", station.distanceKM(to: location.lat, lng2: location.lng) * 0.621371))
|
||||
Text(entry.unit.format(station.distanceKM(to: location.lat, lng2: location.lng)) + " away")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
} else {
|
||||
@@ -192,7 +195,7 @@ struct FuelPriceWidgetView: View {
|
||||
.font(.caption.weight(.semibold))
|
||||
.lineLimit(1)
|
||||
if let location = entry.location {
|
||||
Text(String(format: "%.1f km", station.distanceKM(to: location.lat, lng2: location.lng)))
|
||||
Text(entry.unit.format(station.distanceKM(to: location.lat, lng2: location.lng)))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user