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:
FuelBoard Contributor
2026-08-12 07:46:08 +01:00
parent fb79cf0788
commit 44c6c38536
10 changed files with 284 additions and 47 deletions
+18 -7
View File
@@ -9,6 +9,7 @@ struct ContentView: View {
@State private var selectedFuel: FuelType = FuelStore.loadSelectedFuel()
@State private var sortMode: SortMode = FuelStore.loadSortMode()
@State private var stationLimit: Int = FuelStore.loadStationLimit()
@State private var distanceUnit: DistanceUnit = FuelStore.loadDistanceUnit()
@State private var favourites: [FuelStation] = FuelStore.loadFavourites()
@State private var alertsEnabled: Bool = FuelStore.loadAlertsEnabled()
@State private var alertsRadius: Double = FuelStore.loadAlertsRadius()
@@ -31,7 +32,7 @@ struct ContentView: View {
let selling = stations.filter { $0.prices[selectedFuel] != nil }
guard let location else { return selling }
if sortMode == .closest { return selling } // radius disabled in Closest
let radiusKM = Double(stationLimit) * 1.60934 // chosen miles km
let radiusKM = distanceUnit.toKM(Double(stationLimit)) // chosen units km
return selling.filter {
$0.distanceKM(to: location.lat, lng2: location.lng) <= radiusKM
}
@@ -47,7 +48,7 @@ struct ContentView: View {
private var baselinePrice: Double? {
let pool = poolStations
if sortMode == .closest, let location {
let radiusKM = Double(stationLimit) * 1.60934 // chosen miles km
let radiusKM = distanceUnit.toKM(Double(stationLimit)) // chosen units km
let within = pool.filter {
$0.distanceKM(to: location.lat, lng2: location.lng) <= radiusKM
}
@@ -111,6 +112,7 @@ struct ContentView: View {
selectedFuel: $selectedFuel,
sortMode: $sortMode,
stationLimit: $stationLimit,
distanceUnit: distanceUnit,
baselinePrice: baselinePrice,
topStationID: topStationID,
location: location,
@@ -124,6 +126,7 @@ struct ContentView: View {
favourites: refreshedFavourites,
selectedFuel: selectedFuel,
location: location,
distanceUnit: distanceUnit,
favouriteIDs: favouriteIDs,
onToggleFavourite: toggleFavourite
)
@@ -132,11 +135,17 @@ struct ContentView: View {
AlertsView(
enabled: $alertsEnabled,
radius: $alertsRadius,
distanceUnit: distanceUnit,
monitoredCount: monitor.monitoredStationIDs.count,
lastAlert: monitor.lastAlert,
onShowOnboarding: { showOnboarding = true }
lastAlert: monitor.lastAlert
)
.tabItem { Label("Alerts", systemImage: "bell.fill") }
SettingsView(
distanceUnit: $distanceUnit,
onShowOnboarding: { showOnboarding = true }
)
.tabItem { Label("Settings", systemImage: "gearshape.fill") }
}
.fullScreenCover(isPresented: $showOnboarding) {
OnboardingView {
@@ -191,8 +200,9 @@ struct ContentView: View {
}
.onChange(of: stationLimit) { _, newValue in
// Distance filter is LOCAL math now the cache holds the full-UK
// dump, so changing 5/10/15 miles never needs a network fetch.
// sortedStations/radiusScopedStations recompute on the next render.
// dump, so changing 5/10/15 (miles or km) never needs a network
// fetch. sortedStations/radiusScopedStations recompute on the
// next render.
FuelStore.saveStationLimit(newValue)
WidgetCenter.shared.reloadAllTimelines()
}
@@ -259,6 +269,7 @@ struct StationRow: View {
let station: FuelStation
let fuel: FuelType
let location: Coordinate?
let distanceUnit: DistanceUnit
let baselinePrice: Double?
let isTopResult: Bool
let isFavourite: Bool
@@ -325,7 +336,7 @@ struct StationRow: View {
.lineLimit(1)
.truncationMode(.tail)
if let location {
Text(String(format: "%.1f mi", station.distanceKM(to: location.lat, lng2: location.lng) * 0.621371))
Text(distanceUnit.format(station.distanceKM(to: location.lat, lng2: location.lng)))
.font(.caption2)
.foregroundStyle(.secondary)
.monospacedDigit()