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
+73 -4
View File
@@ -48,21 +48,75 @@ enum RAGRating: Int, Codable {
}
enum FuelType: String, Codable, CaseIterable, Identifiable {
case e10 // Unleaded 95 (E10)
case e5 // Premium 97/98 (E5)
case e10 // Unleaded (E10)
case e5 // Premium (E5)
case diesel // B7 diesel
var id: String { rawValue }
var displayName: String {
switch self {
case .e10: return "Unleaded (E10)"
case .e5: return "Premium (E5)"
case .e10: return "Unleaded"
case .e5: return "Premium"
case .diesel: return "Diesel"
}
}
}
/// Display unit for all distances in the app + widget. Internally distances
/// are always stored/computed in km; conversion happens at the display and
/// filter boundary so nothing else needs to know the unit.
enum DistanceUnit: String, Codable, CaseIterable, Identifiable {
case miles
case kilometers
var id: String { rawValue }
var displayName: String {
switch self {
case .miles: return "Miles"
case .kilometers: return "Kilometres"
}
}
/// Short suffix for values ("5 mi", "3.2 km").
var shortName: String {
switch self {
case .miles: return "mi"
case .kilometers: return "km"
}
}
/// Full word for narrative text ("within 5 miles", "within 8 km").
var label: String {
switch self {
case .miles: return "miles"
case .kilometers: return "km"
}
}
/// Convert a value expressed in this unit to km.
func toKM(_ value: Double) -> Double {
switch self {
case .miles: return value * 1.60934
case .kilometers: return value
}
}
/// Convert a km value to this unit.
func fromKM(_ km: Double) -> Double {
switch self {
case .miles: return km * 0.621371
case .kilometers: return km
}
}
/// Format a km distance in this unit ("1.2 mi", "3.4 km").
func format(_ km: Double) -> String {
String(format: "%.1f %@", fromKM(km), shortName)
}
}
// MARK: - Station model
struct FuelStation: Identifiable, Codable, Equatable {
@@ -179,6 +233,7 @@ struct FuelStore {
static let fuelKey = "fuelboard.selectedFuel" // FuelType raw value
static let sortModeKey = "fuelboard.sortMode" // SortMode raw value
static let stationLimitKey = "fuelboard.stationLimitMiles" // Int miles (5/10/15)
static let distanceUnitKey = "fuelboard.distanceUnit" // DistanceUnit raw value
static let favouritesKey = "fuelboard.favourites" // [FuelStation] JSON
static let alertsEnabledKey = "fuelboard.alertsEnabled" // Bool
static let alertsRadiusKey = "fuelboard.alertsRadius" // Double km
@@ -277,6 +332,20 @@ struct FuelStore {
saveString(String(miles), service: stationLimitKey)
}
// MARK: Distance unit miles or kilometres. Stored raw value; default
// miles for backward compatibility with pre-toggle installs.
static func loadDistanceUnit() -> DistanceUnit {
if let raw = loadString(service: distanceUnitKey), let unit = DistanceUnit(rawValue: raw) {
return unit
}
return .miles
}
static func saveDistanceUnit(_ unit: DistanceUnit) {
saveString(unit.rawValue, service: distanceUnitKey)
}
// MARK: Favourites
static func loadFavourites() -> [FuelStation] {