Labels: pluralize miles (1 mile) and show true distances in the chosen unit (5 mi = 8 km) across Alerts/Stations pickers + captions

This commit is contained in:
FuelBoard Contributor
2026-08-13 15:10:23 +01:00
parent 490c343fd5
commit 2409b71f2f
4 changed files with 51 additions and 7 deletions
+12 -4
View File
@@ -32,8 +32,16 @@ struct AlertsView: View {
/// The Stations-tab search distance shown in the user's unit, e.g.
/// "5 miles" or "8 km" for the "Follow search" rows.
private var searchInUnitLabel: String {
let km = Double(stationLimit) * 1.60934 // stationLimit is always miles
return "\(Int(distanceUnit.fromKM(km).rounded())) \(distanceUnit.label)"
let shown = distanceUnit.displayMiles(stationLimit) // stationLimit is always miles
return "\(shown) \(distanceUnit.label(for: Double(shown)))"
}
/// A miles value (5/10/15) shown truthfully in the user's unit, e.g.
/// "5 miles" (miles unit) or "8 km" (km unit) picker labels must match
/// what the option really means.
private func distanceLabel(forMiles miles: Int) -> String {
let shown = distanceUnit.displayMiles(miles)
return "\(shown) \(distanceUnit.label(for: Double(shown)))"
}
private enum AlertRadiusChoice: Hashable {
@@ -102,7 +110,7 @@ struct AlertsView: View {
Picker("Radius", selection: alertChoice) {
Text("Follow search (\(searchInUnitLabel))").tag(AlertRadiusChoice.followSearch)
ForEach(FuelStore.alertRadiusOptions, id: \.self) { value in
Text("\\(value) \\(distanceUnit.label)").tag(AlertRadiusChoice.fixed(value))
Text("\(value) \(distanceUnit.label(for: Double(value)))").tag(AlertRadiusChoice.fixed(value))
}
}
} header: {
@@ -146,7 +154,7 @@ struct AlertsView: View {
Picker("Distance", selection: laChoice) {
Text("Follow search (\(searchInUnitLabel))").tag(LADistanceChoice.followSearch)
ForEach(FuelStore.stationRadiusOptions, id: \.self) { miles in
Text("\\(miles) \\(distanceUnit.label)").tag(LADistanceChoice.fixed(miles))
Text(distanceLabel(forMiles: miles)).tag(LADistanceChoice.fixed(miles))
}
}
} header: {
+4 -3
View File
@@ -30,11 +30,11 @@ struct StationsView: View {
Section {
if let location {
if sortMode == .closest {
Text("Closest \(selectedFuel.displayName) stations — nearest first, best value within \(stationLimit) \(distanceUnit.label) · \(totalCount) stations")
Text("Closest \(selectedFuel.displayName) stations — nearest first, best value within \(distanceUnit.displayMiles(stationLimit)) \(distanceUnit.label(for: Double(distanceUnit.displayMiles(stationLimit)))) · \(totalCount) stations")
.font(.footnote)
.foregroundStyle(.secondary)
} else {
Text("Cheapest \(selectedFuel.displayName) within \(stationLimit) \(distanceUnit.label)\(totalCount) stations · tap a station for directions.")
Text("Cheapest \(selectedFuel.displayName) within \(distanceUnit.displayMiles(stationLimit)) \(distanceUnit.label(for: Double(distanceUnit.displayMiles(stationLimit))))\(totalCount) stations · tap a station for directions.")
.font(.footnote)
.foregroundStyle(.secondary)
}
@@ -55,7 +55,8 @@ struct StationsView: View {
Section("Distance") {
Picker("Distance", selection: $stationLimit) {
ForEach(FuelStore.stationRadiusOptions, id: \.self) { miles in
Text("\(miles) \(distanceUnit.label)").tag(miles)
let shown = distanceUnit.displayMiles(miles)
Text("\(shown) \(distanceUnit.label(for: Double(shown)))").tag(miles)
}
}
.pickerStyle(.segmented)
@@ -353,3 +353,22 @@ final class AlertRadiusFollowTests: XCTestCase {
XCTAssertEqual(eff, 5 * 1.60934, accuracy: 0.001)
}
}
final class DistanceLabelTests: XCTestCase {
func testUnitLabelPluralizesMiles() {
XCTAssertEqual(DistanceUnit.miles.label(for: 1), "mile")
XCTAssertEqual(DistanceUnit.miles.label(for: 2), "miles")
XCTAssertEqual(DistanceUnit.miles.label(for: 5), "miles")
XCTAssertEqual(DistanceUnit.kilometers.label(for: 1), "km")
XCTAssertEqual(DistanceUnit.kilometers.label(for: 8), "km")
}
func testDisplayMilesShowsTrueDistance() {
// Picker labels must match what the option really means.
XCTAssertEqual(DistanceUnit.miles.displayMiles(5), 5)
XCTAssertEqual(DistanceUnit.miles.displayMiles(15), 15)
XCTAssertEqual(DistanceUnit.kilometers.displayMiles(5), 8) // 5 mi = 8.05 km
XCTAssertEqual(DistanceUnit.kilometers.displayMiles(10), 16) // 10 mi = 16.1 km
XCTAssertEqual(DistanceUnit.kilometers.displayMiles(15), 24) // 15 mi = 24.1 km
}
}
+16
View File
@@ -105,6 +105,22 @@ enum DistanceUnit: String, Codable, CaseIterable, Identifiable {
}
}
/// Full word for a specific value pluralizes miles ("1 mile" vs
/// "5 miles"); metric is always "km".
func label(for value: Double) -> String {
switch self {
case .miles: return value == 1 ? "mile" : "miles"
case .kilometers: return "km"
}
}
/// A whole-mile distance (as stored/used by search + Live Activity)
/// shown in this unit, rounded to a whole number for picker labels:
/// 5 miles -> "5" (miles unit) or "8" (km unit).
func displayMiles(_ miles: Int) -> Int {
Int(fromKM(Double(miles) * 1.60934).rounded())
}
/// Convert a value expressed in this unit to km.
func toKM(_ value: Double) -> Double {
switch self {