Clarify fuel savings comparison labels
This commit is contained in:
@@ -533,7 +533,7 @@
|
|||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Config/WatchApp.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Config/WatchApp.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 26;
|
CURRENT_PROJECT_VERSION = 27;
|
||||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@@ -599,7 +599,7 @@
|
|||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Config/WatchApp.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Config/WatchApp.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 26;
|
CURRENT_PROJECT_VERSION = 27;
|
||||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@@ -663,7 +663,7 @@
|
|||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 26;
|
CURRENT_PROJECT_VERSION = 27;
|
||||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@@ -728,7 +728,7 @@
|
|||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 26;
|
CURRENT_PROJECT_VERSION = 27;
|
||||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@@ -826,7 +826,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Config/App.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Config/App.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 26;
|
CURRENT_PROJECT_VERSION = 27;
|
||||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = "Config/App-Info.plist";
|
INFOPLIST_FILE = "Config/App-Info.plist";
|
||||||
@@ -851,7 +851,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Config/App.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Config/App.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 26;
|
CURRENT_PROJECT_VERSION = 27;
|
||||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = "Config/App-Info.plist";
|
INFOPLIST_FILE = "Config/App-Info.plist";
|
||||||
@@ -875,7 +875,7 @@
|
|||||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Config/Widget.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Config/Widget.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 26;
|
CURRENT_PROJECT_VERSION = 27;
|
||||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = "Config/Widget-Info.plist";
|
INFOPLIST_FILE = "Config/Widget-Info.plist";
|
||||||
@@ -900,7 +900,7 @@
|
|||||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Config/Widget.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Config/Widget.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 26;
|
CURRENT_PROJECT_VERSION = 27;
|
||||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = "Config/Widget-Info.plist";
|
INFOPLIST_FILE = "Config/Widget-Info.plist";
|
||||||
|
|||||||
@@ -1523,7 +1523,6 @@ struct ContentView: View {
|
|||||||
fuelSavingsSettings: fuelSavingsSettings,
|
fuelSavingsSettings: fuelSavingsSettings,
|
||||||
fuelSavingsAmount: $sessionFuelSavingsAmount,
|
fuelSavingsAmount: $sessionFuelSavingsAmount,
|
||||||
savingsForStation: savingsLookup(baseline: baselineStation, journeys: scaffold.journeys),
|
savingsForStation: savingsLookup(baseline: baselineStation, journeys: scaffold.journeys),
|
||||||
savingsBaselineStationName: baselineStation?.name,
|
|
||||||
journeyDiagnosticsForStation: { station in journeyDiagnostics(for: station, baseline: baselineStation) },
|
journeyDiagnosticsForStation: { station in journeyDiagnostics(for: station, baseline: baselineStation) },
|
||||||
onToggleFavourite: toggleFavourite,
|
onToggleFavourite: toggleFavourite,
|
||||||
onRefresh: { await refresh(force: true) }
|
onRefresh: { await refresh(force: true) }
|
||||||
@@ -1545,7 +1544,6 @@ struct ContentView: View {
|
|||||||
fuelSavingsSettings: fuelSavingsSettings,
|
fuelSavingsSettings: fuelSavingsSettings,
|
||||||
fuelSavingsAmount: $sessionFuelSavingsAmount,
|
fuelSavingsAmount: $sessionFuelSavingsAmount,
|
||||||
savingsForStation: savingsLookup(baseline: baselineStation, journeys: scaffold.journeys),
|
savingsForStation: savingsLookup(baseline: baselineStation, journeys: scaffold.journeys),
|
||||||
savingsBaselineStationName: baselineStation?.name,
|
|
||||||
journeyDiagnosticsForStation: { station in journeyDiagnostics(for: station, baseline: baselineStation) },
|
journeyDiagnosticsForStation: { station in journeyDiagnostics(for: station, baseline: baselineStation) },
|
||||||
onToggleFavourite: toggleFavourite,
|
onToggleFavourite: toggleFavourite,
|
||||||
onReorder: reorderFavourites,
|
onReorder: reorderFavourites,
|
||||||
@@ -1944,7 +1942,6 @@ struct StationRow: View {
|
|||||||
FuelSavingsSummaryView(
|
FuelSavingsSummaryView(
|
||||||
availability: fuelSavings,
|
availability: fuelSavings,
|
||||||
amount: fuelSavingsAmount,
|
amount: fuelSavingsAmount,
|
||||||
baselineStationName: nil,
|
|
||||||
compact: true,
|
compact: true,
|
||||||
distanceUnit: distanceUnit
|
distanceUnit: distanceUnit
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ struct FavouritesView: View {
|
|||||||
/// Estimated saving for a favourite, against the nearest eligible station —
|
/// Estimated saving for a favourite, against the nearest eligible station —
|
||||||
/// the same baseline and definition the Stations list uses.
|
/// the same baseline and definition the Stations list uses.
|
||||||
var savingsForStation: (FuelStation) -> FuelSavingsAvailability? = { _ in nil }
|
var savingsForStation: (FuelStation) -> FuelSavingsAvailability? = { _ in nil }
|
||||||
var savingsBaselineStationName: String?
|
|
||||||
var journeyDiagnosticsForStation: (FuelStation) -> FuelSavingsJourneyDiagnostics? = { _ in nil }
|
var journeyDiagnosticsForStation: (FuelStation) -> FuelSavingsJourneyDiagnostics? = { _ in nil }
|
||||||
var onToggleFavourite: (FuelStation, FuelType) -> Void = { _, _ in }
|
var onToggleFavourite: (FuelStation, FuelType) -> Void = { _, _ in }
|
||||||
/// Persists a reordered favourites array (after drag-and-drop).
|
/// Persists a reordered favourites array (after drag-and-drop).
|
||||||
@@ -84,7 +83,6 @@ struct FavouritesView: View {
|
|||||||
fuelSavingsSettings: FuelSavingsSettings = .defaults,
|
fuelSavingsSettings: FuelSavingsSettings = .defaults,
|
||||||
fuelSavingsAmount: Binding<FuelSavingsFillAmount>,
|
fuelSavingsAmount: Binding<FuelSavingsFillAmount>,
|
||||||
savingsForStation: @escaping (FuelStation) -> FuelSavingsAvailability? = { _ in nil },
|
savingsForStation: @escaping (FuelStation) -> FuelSavingsAvailability? = { _ in nil },
|
||||||
savingsBaselineStationName: String? = nil,
|
|
||||||
journeyDiagnosticsForStation: @escaping (FuelStation) -> FuelSavingsJourneyDiagnostics? = { _ in nil },
|
journeyDiagnosticsForStation: @escaping (FuelStation) -> FuelSavingsJourneyDiagnostics? = { _ in nil },
|
||||||
onToggleFavourite: @escaping (FuelStation, FuelType) -> Void = { _, _ in },
|
onToggleFavourite: @escaping (FuelStation, FuelType) -> Void = { _, _ in },
|
||||||
onReorder: @escaping ([FavouriteEntry]) -> Void = { _ in },
|
onReorder: @escaping ([FavouriteEntry]) -> Void = { _ in },
|
||||||
@@ -99,7 +97,6 @@ struct FavouritesView: View {
|
|||||||
self.fuelSavingsSettings = fuelSavingsSettings
|
self.fuelSavingsSettings = fuelSavingsSettings
|
||||||
_fuelSavingsAmount = fuelSavingsAmount
|
_fuelSavingsAmount = fuelSavingsAmount
|
||||||
self.savingsForStation = savingsForStation
|
self.savingsForStation = savingsForStation
|
||||||
self.savingsBaselineStationName = savingsBaselineStationName
|
|
||||||
self.journeyDiagnosticsForStation = journeyDiagnosticsForStation
|
self.journeyDiagnosticsForStation = journeyDiagnosticsForStation
|
||||||
self.onToggleFavourite = onToggleFavourite
|
self.onToggleFavourite = onToggleFavourite
|
||||||
self.onReorder = onReorder
|
self.onReorder = onReorder
|
||||||
@@ -221,7 +218,6 @@ struct FavouritesView: View {
|
|||||||
distanceUnit: distanceUnit,
|
distanceUnit: distanceUnit,
|
||||||
fuelSavings: savingsForStation(station),
|
fuelSavings: savingsForStation(station),
|
||||||
fuelSavingsAmount: fuelSavingsAmount,
|
fuelSavingsAmount: fuelSavingsAmount,
|
||||||
savingsBaselineStationName: savingsBaselineStationName,
|
|
||||||
journeyDiagnostics: journeyDiagnosticsForStation(station),
|
journeyDiagnostics: journeyDiagnosticsForStation(station),
|
||||||
// Every station listed here IS a favourite for the tab's
|
// Every station listed here IS a favourite for the tab's
|
||||||
// fuel, so the star starts filled; tapping it unstars.
|
// fuel, so the star starts filled; tapping it unstars.
|
||||||
|
|||||||
@@ -73,13 +73,17 @@ struct FuelSavingsAdjustmentView: View {
|
|||||||
struct FuelSavingsSummaryView: View {
|
struct FuelSavingsSummaryView: View {
|
||||||
let availability: FuelSavingsAvailability?
|
let availability: FuelSavingsAvailability?
|
||||||
let amount: FuelSavingsFillAmount
|
let amount: FuelSavingsFillAmount
|
||||||
let baselineStationName: String?
|
var baselineStationName: String? = nil
|
||||||
var compact: Bool = false
|
var compact: Bool = false
|
||||||
/// Sizes the "extra return travel" caption in the user's own unit (the
|
/// Sizes the "extra return travel" caption in the user's own unit (the
|
||||||
/// stored figure is miles) — used by both the row banner and the detail sheet.
|
/// stored figure is miles) — used by both the row banner and the detail sheet.
|
||||||
var distanceUnit: DistanceUnit = .miles
|
var distanceUnit: DistanceUnit = .miles
|
||||||
var showInfoAction: (() -> Void)? = nil
|
var showInfoAction: (() -> Void)? = nil
|
||||||
|
|
||||||
|
private func comparisonLabel(for result: FuelSavingsResult) -> String {
|
||||||
|
FuelSavingsFormat.comparisonLabel(status: result.status)
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
switch availability {
|
switch availability {
|
||||||
case .available(let result):
|
case .available(let result):
|
||||||
@@ -93,8 +97,17 @@ struct FuelSavingsSummaryView: View {
|
|||||||
// break even, blue baseline. lineLimit + minimumScaleFactor keep
|
// break even, blue baseline. lineLimit + minimumScaleFactor keep
|
||||||
// it to one line, so rows stay even.
|
// it to one line, so rows stay even.
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
capsule(for: result, compact: true)
|
HStack(alignment: .firstTextBaseline, spacing: 6) {
|
||||||
.accessibilityLabel(accessibilityHeadline(for: result))
|
capsule(for: result, compact: true)
|
||||||
|
.accessibilityLabel(accessibilityHeadline(for: result))
|
||||||
|
Text(comparisonLabel(for: result))
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.75)
|
||||||
|
.layoutPriority(1)
|
||||||
|
.accessibilityLabel("Comparison: \(comparisonLabel(for: result))")
|
||||||
|
}
|
||||||
if let extraTravel = FuelSavingsFormat.extraTravelLabel(
|
if let extraTravel = FuelSavingsFormat.extraTravelLabel(
|
||||||
extraDistanceMiles: result.extraDistanceMiles,
|
extraDistanceMiles: result.extraDistanceMiles,
|
||||||
unit: distanceUnit,
|
unit: distanceUnit,
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ struct StationsView: View {
|
|||||||
let fuelSavingsSettings: FuelSavingsSettings
|
let fuelSavingsSettings: FuelSavingsSettings
|
||||||
@Binding var fuelSavingsAmount: FuelSavingsFillAmount
|
@Binding var fuelSavingsAmount: FuelSavingsFillAmount
|
||||||
var savingsForStation: (FuelStation) -> FuelSavingsAvailability? = { _ in nil }
|
var savingsForStation: (FuelStation) -> FuelSavingsAvailability? = { _ in nil }
|
||||||
var savingsBaselineStationName: String?
|
|
||||||
var journeyDiagnosticsForStation: (FuelStation) -> FuelSavingsJourneyDiagnostics? = { _ in nil }
|
var journeyDiagnosticsForStation: (FuelStation) -> FuelSavingsJourneyDiagnostics? = { _ in nil }
|
||||||
var onToggleFavourite: (FuelStation, FuelType) -> Void = { _, _ in }
|
var onToggleFavourite: (FuelStation, FuelType) -> Void = { _, _ in }
|
||||||
var onRefresh: () async -> Void = {}
|
var onRefresh: () async -> Void = {}
|
||||||
@@ -348,7 +347,6 @@ struct StationsView: View {
|
|||||||
distanceUnit: distanceUnit,
|
distanceUnit: distanceUnit,
|
||||||
fuelSavings: savingsForStation(station),
|
fuelSavings: savingsForStation(station),
|
||||||
fuelSavingsAmount: fuelSavingsAmount,
|
fuelSavingsAmount: fuelSavingsAmount,
|
||||||
savingsBaselineStationName: savingsBaselineStationName,
|
|
||||||
journeyDiagnostics: journeyDiagnosticsForStation(station),
|
journeyDiagnostics: journeyDiagnosticsForStation(station),
|
||||||
isFavourite: favouriteIDs.contains(station.id),
|
isFavourite: favouriteIDs.contains(station.id),
|
||||||
onToggleFavourite: { onToggleFavourite(station, selectedFuel) }
|
onToggleFavourite: { onToggleFavourite(station, selectedFuel) }
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ struct WidgetMockScreen: View {
|
|||||||
let location = FuelStore.loadLocation().map { Coordinate(lat: $0.lat, lng: $0.lng) }
|
let location = FuelStore.loadLocation().map { Coordinate(lat: $0.lat, lng: $0.lng) }
|
||||||
// 5 miles — the widget config's default Distance. WidgetDistance itself
|
// 5 miles — the widget config's default Distance. WidgetDistance itself
|
||||||
// lives in the widget target, so the preview mirrors its default here.
|
// lives in the widget target, so the preview mirrors its default here.
|
||||||
let radiusKM = 5 * 1.60934
|
let radiusKM = 5 * FuelSavingsCalculator.kilometresPerMile
|
||||||
let all = FuelStore.loadStations()
|
let all = FuelStore.loadStations()
|
||||||
let selling = all.filter { station in
|
let selling = all.filter { station in
|
||||||
guard station.prices[fuel] != nil else { return false }
|
guard station.prices[fuel] != nil else { return false }
|
||||||
|
|||||||
@@ -211,10 +211,10 @@ final class DistanceTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testMilesConversion() {
|
func testMilesConversion() {
|
||||||
// 5 miles ≈ 8.05 km (app uses × 1.60934)
|
// 5 miles ≈ 8.05 km using the shared exact conversion
|
||||||
XCTAssertEqual(5 * 1.60934, 8.0467, accuracy: 0.001)
|
XCTAssertEqual(5 * FuelSavingsCalculator.kilometresPerMile, 8.04672, accuracy: 0.001)
|
||||||
// 262 km ≈ 162.8 miles (display uses × 0.621371)
|
// 262 km ≈ 162.8 miles using the shared exact conversion
|
||||||
XCTAssertEqual(262 * 0.621371, 162.8, accuracy: 0.1)
|
XCTAssertEqual(262 / FuelSavingsCalculator.kilometresPerMile, 162.8, accuracy: 0.1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,14 +267,14 @@ final class FuelTypeLabelTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testDistanceUnitConversion() {
|
func testDistanceUnitConversion() {
|
||||||
XCTAssertEqual(DistanceUnit.miles.toKM(1), 1.60934, accuracy: 0.00001)
|
XCTAssertEqual(DistanceUnit.miles.toKM(1), 1.609344, accuracy: 0.00001)
|
||||||
XCTAssertEqual(DistanceUnit.kilometers.toKM(5), 5)
|
XCTAssertEqual(DistanceUnit.kilometers.toKM(5), 5)
|
||||||
XCTAssertEqual(DistanceUnit.miles.fromKM(1.60934), 1, accuracy: 0.00001)
|
XCTAssertEqual(DistanceUnit.miles.fromKM(1.609344), 1, accuracy: 0.00001)
|
||||||
XCTAssertEqual(DistanceUnit.kilometers.fromKM(2.5), 2.5)
|
XCTAssertEqual(DistanceUnit.kilometers.fromKM(2.5), 2.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDistanceUnitFormat() {
|
func testDistanceUnitFormat() {
|
||||||
XCTAssertEqual(DistanceUnit.miles.format(1.60934), "1.0 mi")
|
XCTAssertEqual(DistanceUnit.miles.format(1.609344), "1.0 mi")
|
||||||
XCTAssertEqual(DistanceUnit.kilometers.format(3.4), "3.4 km")
|
XCTAssertEqual(DistanceUnit.kilometers.format(3.4), "3.4 km")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,7 +400,7 @@ final class AlertRadiusFollowTests: XCTestCase {
|
|||||||
FuelStore.saveDistanceUnit(.miles)
|
FuelStore.saveDistanceUnit(.miles)
|
||||||
FuelStore.saveStationLimit(5)
|
FuelStore.saveStationLimit(5)
|
||||||
let eff = FuelStore.effectiveAlertsRadiusKM(followsSearch: true, manualKM: 3)
|
let eff = FuelStore.effectiveAlertsRadiusKM(followsSearch: true, manualKM: 3)
|
||||||
XCTAssertEqual(eff, 5 * 1.60934, accuracy: 0.001)
|
XCTAssertEqual(eff, 5 * FuelSavingsCalculator.kilometresPerMile, accuracy: 0.001)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -731,7 +731,7 @@ final class RoadDistanceCacheTests: XCTestCase {
|
|||||||
sourceLat: sourceLat, sourceLng: sourceLng,
|
sourceLat: sourceLat, sourceLng: sourceLng,
|
||||||
entries: ["far": .init(meters: 23_500, lat: 51.5074, lng: -0.1278, returnMeters: 23_500)])
|
entries: ["far": .init(meters: 23_500, lat: 51.5074, lng: -0.1278, returnMeters: 23_500)])
|
||||||
|
|
||||||
let tenMilesKM = 10 * 1.60934
|
let tenMilesKM = 10 * FuelSavingsCalculator.kilometresPerMile
|
||||||
XCTAssertLessThan(far.distanceKM(to: sourceLat, lng2: sourceLng), tenMilesKM,
|
XCTAssertLessThan(far.distanceKM(to: sourceLat, lng2: sourceLng), tenMilesKM,
|
||||||
"fixture: the straight line must sit INSIDE the 10-mile radius")
|
"fixture: the straight line must sit INSIDE the 10-mile radius")
|
||||||
XCTAssertGreaterThan(FuelStore.displayDistanceKM(station: far, userLat: sourceLat, userLng: sourceLng),
|
XCTAssertGreaterThan(FuelStore.displayDistanceKM(station: far, userLat: sourceLat, userLng: sourceLng),
|
||||||
@@ -743,7 +743,7 @@ final class RoadDistanceCacheTests: XCTestCase {
|
|||||||
userLat: sourceLat, userLng: sourceLng),
|
userLat: sourceLat, userLng: sourceLng),
|
||||||
"a station whose row reads 14.6 mi must not appear in a 10-mile list")
|
"a station whose row reads 14.6 mi must not appear in a 10-mile list")
|
||||||
// The same station is back once the radius actually covers its distance.
|
// The same station is back once the radius actually covers its distance.
|
||||||
XCTAssertTrue(FuelStore.isWithinDisplayRadius(station: far, radiusKM: 15 * 1.60934,
|
XCTAssertTrue(FuelStore.isWithinDisplayRadius(station: far, radiusKM: 15 * FuelSavingsCalculator.kilometresPerMile,
|
||||||
userLat: sourceLat, userLng: sourceLng))
|
userLat: sourceLat, userLng: sourceLng))
|
||||||
// …and an unknown station (no cached route) still tests its displayed
|
// …and an unknown station (no cached route) still tests its displayed
|
||||||
// straight line, so nothing changes for the bulk of the list.
|
// straight line, so nothing changes for the bulk of the list.
|
||||||
@@ -783,10 +783,10 @@ final class RoadDistanceCacheTests: XCTestCase {
|
|||||||
XCTAssertFalse(confirmed.isApproximate, "a cached road distance is not an approximation")
|
XCTAssertFalse(confirmed.isApproximate, "a cached road distance is not an approximation")
|
||||||
XCTAssertEqual(confirmed.km, 3.2, accuracy: 0.0001)
|
XCTAssertEqual(confirmed.km, 3.2, accuracy: 0.0001)
|
||||||
XCTAssertEqual(confirmed.label(unit: .miles), "2.0 mi")
|
XCTAssertEqual(confirmed.label(unit: .miles), "2.0 mi")
|
||||||
XCTAssertEqual(confirmed.labelWithReturn(unit: .miles), "2.0 mi (4.0 mi return)")
|
XCTAssertEqual(confirmed.labelWithReturn(unit: .miles), "2.0 mi (4.2 mi return)")
|
||||||
XCTAssertEqual(confirmed.labelWithReturn(unit: .kilometers), "3.2 km (6.4 km return)")
|
XCTAssertEqual(confirmed.labelWithReturn(unit: .kilometers), "3.2 km (6.8 km return)")
|
||||||
XCTAssertEqual(confirmed.spokenLabel(unit: .miles), "2.0 miles")
|
XCTAssertEqual(confirmed.spokenLabel(unit: .miles), "2.0 miles")
|
||||||
XCTAssertEqual(confirmed.spokenLabelWithReturn(unit: .miles), "2.0 miles, 4.0 miles return")
|
XCTAssertEqual(confirmed.spokenLabelWithReturn(unit: .miles), "2.0 miles, 4.2 miles return")
|
||||||
|
|
||||||
let approximate = FuelStore.stationDistanceDisplay(station: unrouted, userLat: 51.6, userLng: -0.1)
|
let approximate = FuelStore.stationDistanceDisplay(station: unrouted, userLat: 51.6, userLng: -0.1)
|
||||||
XCTAssertTrue(approximate.isApproximate, "the straight-line fallback must be identified as one")
|
XCTAssertTrue(approximate.isApproximate, "the straight-line fallback must be identified as one")
|
||||||
@@ -794,11 +794,40 @@ final class RoadDistanceCacheTests: XCTestCase {
|
|||||||
XCTAssertEqual(approximate.label(unit: .miles),
|
XCTAssertEqual(approximate.label(unit: .miles),
|
||||||
"Approx. " + DistanceUnit.miles.format(approximate.km))
|
"Approx. " + DistanceUnit.miles.format(approximate.km))
|
||||||
XCTAssertEqual(approximate.labelWithReturn(unit: .miles),
|
XCTAssertEqual(approximate.labelWithReturn(unit: .miles),
|
||||||
"Approx. \(DistanceUnit.miles.format(approximate.km)) (\(DistanceUnit.miles.format(approximate.km * 2)) return)")
|
"Approx. " + DistanceUnit.miles.format(approximate.km) + " (Approx. " + DistanceUnit.miles.format(approximate.km * 2) + " return)")
|
||||||
XCTAssertEqual(approximate.spokenLabel(unit: .kilometers),
|
XCTAssertEqual(approximate.spokenLabel(unit: .kilometers),
|
||||||
"Approximately " + DistanceUnit.kilometers.spoken(approximate.km))
|
"Approximately " + DistanceUnit.kilometers.spoken(approximate.km))
|
||||||
XCTAssertEqual(approximate.spokenLabelWithReturn(unit: .kilometers),
|
XCTAssertEqual(approximate.spokenLabelWithReturn(unit: .kilometers),
|
||||||
"Approximately \(DistanceUnit.kilometers.spoken(approximate.km)), \(DistanceUnit.kilometers.spoken(approximate.km * 2)) return")
|
"Approximately \(DistanceUnit.kilometers.spoken(approximate.km)), Approximately \(DistanceUnit.kilometers.spoken(approximate.km * 2)) return")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDisplayedReturnUsesTheIndependentlyMeasuredReturnRoute() {
|
||||||
|
FuelStore.setAccurateDistancesEnabled(true)
|
||||||
|
let station = station("separate-legs", 51.5074, -0.1278)
|
||||||
|
FuelStore.saveRoadDistances(
|
||||||
|
sourceLat: 51.6,
|
||||||
|
sourceLng: -0.1,
|
||||||
|
entries: ["separate-legs": .init(
|
||||||
|
meters: 4_650,
|
||||||
|
lat: station.lat,
|
||||||
|
lng: station.lng,
|
||||||
|
returnMeters: 4_820
|
||||||
|
)],
|
||||||
|
complete: true
|
||||||
|
)
|
||||||
|
|
||||||
|
let display = FuelStore.stationDistanceDisplay(
|
||||||
|
station: station,
|
||||||
|
userLat: 51.6,
|
||||||
|
userLng: -0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
XCTAssertEqual(display.km, 4.65, accuracy: 1e-12)
|
||||||
|
XCTAssertEqual(display.returnKM, 9.47, accuracy: 1e-12)
|
||||||
|
XCTAssertFalse(display.isApproximate)
|
||||||
|
XCTAssertFalse(display.returnIsApproximate)
|
||||||
|
XCTAssertEqual(display.labelWithReturn(unit: .miles), "2.9 mi (5.9 mi return)")
|
||||||
|
XCTAssertEqual(display.spokenLabelWithReturn(unit: .miles), "2.9 miles, 5.9 miles return")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The marker tracks the cache on its own: same station, bare figure once its
|
/// The marker tracks the cache on its own: same station, bare figure once its
|
||||||
@@ -961,7 +990,7 @@ final class RoadDistanceCacheTests: XCTestCase {
|
|||||||
let display = FuelStore.stationDistanceDisplay(station: routed, userLat: 51.6, userLng: -0.1)
|
let display = FuelStore.stationDistanceDisplay(station: routed, userLat: 51.6, userLng: -0.1)
|
||||||
XCTAssertTrue(display.isApproximate, "the browse layer must never serve routes with the mode off")
|
XCTAssertTrue(display.isApproximate, "the browse layer must never serve routes with the mode off")
|
||||||
XCTAssertEqual(display.km, routed.distanceKM(to: 51.6, lng2: -0.1), accuracy: 0.0001)
|
XCTAssertEqual(display.km, routed.distanceKM(to: 51.6, lng2: -0.1), accuracy: 0.0001)
|
||||||
XCTAssertTrue(FuelStore.isWithinDisplayRadius(station: routed, radiusKM: 10 * 1.60934,
|
XCTAssertTrue(FuelStore.isWithinDisplayRadius(station: routed, radiusKM: 10 * FuelSavingsCalculator.kilometresPerMile,
|
||||||
userLat: 51.6, userLng: -0.1),
|
userLat: 51.6, userLng: -0.1),
|
||||||
"with the mode off the radius is a straight-line radius, agreeing with the rows")
|
"with the mode off the radius is a straight-line radius, agreeing with the rows")
|
||||||
|
|
||||||
@@ -1000,7 +1029,7 @@ final class RoadDistanceCacheTests: XCTestCase {
|
|||||||
let display = FuelStore.stationDistanceDisplay(station: routed, userLat: 51.6, userLng: -0.1)
|
let display = FuelStore.stationDistanceDisplay(station: routed, userLat: 51.6, userLng: -0.1)
|
||||||
XCTAssertFalse(display.isApproximate)
|
XCTAssertFalse(display.isApproximate)
|
||||||
XCTAssertEqual(display.km, 23.5, accuracy: 0.0001)
|
XCTAssertEqual(display.km, 23.5, accuracy: 0.0001)
|
||||||
XCTAssertFalse(FuelStore.isWithinDisplayRadius(station: routed, radiusKM: 10 * 1.60934,
|
XCTAssertFalse(FuelStore.isWithinDisplayRadius(station: routed, radiusKM: 10 * FuelSavingsCalculator.kilometresPerMile,
|
||||||
userLat: 51.6, userLng: -0.1),
|
userLat: 51.6, userLng: -0.1),
|
||||||
"in this mode a 10-mile radius is 10 miles of driving, matching the routed rows")
|
"in this mode a 10-mile radius is 10 miles of driving, matching the routed rows")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,6 +263,23 @@ final class FuelSavingsCalculationAuditTests: XCTestCase {
|
|||||||
XCTAssertEqual(FuelSavingsFormat.money(0), "£0.00")
|
XCTAssertEqual(FuelSavingsFormat.money(0), "£0.00")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testIndependentlyRoundedFinancialComponentsMayDifferFromRoundedTotalByOnePenny() {
|
||||||
|
let gross = 1.234
|
||||||
|
let drivingCost = 0.005
|
||||||
|
let net = gross - drivingCost
|
||||||
|
|
||||||
|
XCTAssertEqual(FuelSavingsFormat.money(gross), "£1.23")
|
||||||
|
XCTAssertEqual(FuelSavingsFormat.money(drivingCost), "£0.01")
|
||||||
|
XCTAssertEqual(FuelSavingsFormat.money(net), "£1.23")
|
||||||
|
let independentlyRoundedSubtraction = 1.23 - 0.01
|
||||||
|
XCTAssertEqual(independentlyRoundedSubtraction, 1.22, accuracy: 1e-12)
|
||||||
|
XCTAssertNotEqual(
|
||||||
|
FuelSavingsFormat.money(independentlyRoundedSubtraction),
|
||||||
|
FuelSavingsFormat.money(net),
|
||||||
|
"the raw total remains authoritative; independently rounded components need not add back exactly"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func testSortingUsesTheUnroundedNetSoRoundedTiesStillOrderCorrectly() {
|
func testSortingUsesTheUnroundedNetSoRoundedTiesStillOrderCorrectly() {
|
||||||
// Two rows that both DISPLAY "£0.18 worse off" but differ in the raw
|
// Two rows that both DISPLAY "£0.18 worse off" but differ in the raw
|
||||||
// figure: the sort must use the raw value, never the shown one.
|
// figure: the sort must use the raw value, never the shown one.
|
||||||
|
|||||||
@@ -454,6 +454,25 @@ final class FuelSavingsEstimateTests: XCTestCase {
|
|||||||
|
|
||||||
// MARK: Row copy
|
// MARK: Row copy
|
||||||
|
|
||||||
|
func testCompactComparisonLabelUsesGenericNearestStationWording() {
|
||||||
|
XCTAssertEqual(
|
||||||
|
FuelSavingsFormat.comparisonLabel(status: .worthTheDrive),
|
||||||
|
"vs nearest station"
|
||||||
|
)
|
||||||
|
XCTAssertEqual(
|
||||||
|
FuelSavingsFormat.comparisonLabel(status: .notWorthTheDrive),
|
||||||
|
"vs nearest station"
|
||||||
|
)
|
||||||
|
XCTAssertEqual(
|
||||||
|
FuelSavingsFormat.comparisonLabel(status: .breakEven),
|
||||||
|
"vs nearest station"
|
||||||
|
)
|
||||||
|
XCTAssertEqual(
|
||||||
|
FuelSavingsFormat.comparisonLabel(status: .baseline),
|
||||||
|
"your baseline"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// The row's extra-driving caption must follow the DISPLAY unit: the stored
|
/// The row's extra-driving caption must follow the DISPLAY unit: the stored
|
||||||
/// figure is always miles, and converting by the current unit instead would
|
/// figure is always miles, and converting by the current unit instead would
|
||||||
/// leave a km user's figure unlabelled and 1.6× too large.
|
/// leave a km user's figure unlabelled and 1.6× too large.
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ private enum WatchDistanceUnit: String, Codable {
|
|||||||
func format(km: Double) -> String {
|
func format(km: Double) -> String {
|
||||||
switch self {
|
switch self {
|
||||||
case .miles:
|
case .miles:
|
||||||
return String(format: "%.1f mi", km * 0.621371)
|
return String(format: "%.1f mi", km / 1.609344)
|
||||||
case .kilometers:
|
case .kilometers:
|
||||||
return String(format: "%.1f km", km)
|
return String(format: "%.1f km", km)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
|
|||||||
let fuel = FuelType(rawValue: configuration.fuel.rawValue) ?? .e10
|
let fuel = FuelType(rawValue: configuration.fuel.rawValue) ?? .e10
|
||||||
let isFavourites = configuration.sort == .favourites
|
let isFavourites = configuration.sort == .favourites
|
||||||
let sort: SortMode = configuration.sort == .closest ? .closest : .cheapest
|
let sort: SortMode = configuration.sort == .closest ? .closest : .cheapest
|
||||||
let radiusKM = Double(configuration.distance.miles) * 1.60934
|
let radiusKM = Double(configuration.distance.miles) * FuelSavingsCalculator.kilometresPerMile
|
||||||
|
|
||||||
// 1) Try a fresh location fix (bounded to a few seconds). The fix's own
|
// 1) Try a fresh location fix (bounded to a few seconds). The fix's own
|
||||||
// accuracy is kept: the extension runs its own CLLocationManager and is
|
// accuracy is kept: the extension runs its own CLLocationManager and is
|
||||||
|
|||||||
@@ -143,6 +143,13 @@ enum FuelSavingsFormat {
|
|||||||
: "\(distanceText) extra return travel"
|
: "\(distanceText) extra return travel"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compact comparison copy kept outside the coloured verdict capsule.
|
||||||
|
/// "Nearest" refers to the existing shortest-journey baseline selection;
|
||||||
|
/// it does not claim a straight-line comparison.
|
||||||
|
static func comparisonLabel(status: FuelSavingsStatus) -> String {
|
||||||
|
status == .baseline ? "your baseline" : "vs nearest station"
|
||||||
|
}
|
||||||
|
|
||||||
/// VoiceOver form of `extraTravelLabel`. The abbreviation and the unit suffix
|
/// VoiceOver form of `extraTravelLabel`. The abbreviation and the unit suffix
|
||||||
/// are spoken as words ("Approximately 6.4 miles extra return travel") because
|
/// are spoken as words ("Approximately 6.4 miles extra return travel") because
|
||||||
/// synthesizers render "Approx." and "mi" inconsistently.
|
/// synthesizers render "Approx." and "mi" inconsistently.
|
||||||
|
|||||||
+59
-32
@@ -183,13 +183,13 @@ enum DistanceUnit: String, Codable, CaseIterable, Identifiable {
|
|||||||
/// shown in this unit, rounded to a whole number for picker labels:
|
/// shown in this unit, rounded to a whole number for picker labels:
|
||||||
/// 5 miles -> "5" (miles unit) or "8" (km unit).
|
/// 5 miles -> "5" (miles unit) or "8" (km unit).
|
||||||
func displayMiles(_ miles: Int) -> Int {
|
func displayMiles(_ miles: Int) -> Int {
|
||||||
Int(fromKM(Double(miles) * 1.60934).rounded())
|
Int(fromKM(Double(miles) * FuelSavingsCalculator.kilometresPerMile).rounded())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a value expressed in this unit to km.
|
/// Convert a value expressed in this unit to km.
|
||||||
func toKM(_ value: Double) -> Double {
|
func toKM(_ value: Double) -> Double {
|
||||||
switch self {
|
switch self {
|
||||||
case .miles: return value * 1.60934
|
case .miles: return value * FuelSavingsCalculator.kilometresPerMile
|
||||||
case .kilometers: return value
|
case .kilometers: return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +197,7 @@ enum DistanceUnit: String, Codable, CaseIterable, Identifiable {
|
|||||||
/// Convert a km value to this unit.
|
/// Convert a km value to this unit.
|
||||||
func fromKM(_ km: Double) -> Double {
|
func fromKM(_ km: Double) -> Double {
|
||||||
switch self {
|
switch self {
|
||||||
case .miles: return km * 0.621371
|
case .miles: return km / FuelSavingsCalculator.kilometresPerMile
|
||||||
case .kilometers: return km
|
case .kilometers: return km
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -979,7 +979,7 @@ struct FuelStore {
|
|||||||
|
|
||||||
/// "Follow search" caps the geofence at 8 miles so a 10/15-mile Stations
|
/// "Follow search" caps the geofence at 8 miles so a 10/15-mile Stations
|
||||||
/// search never creates huge region-monitoring circles.
|
/// search never creates huge region-monitoring circles.
|
||||||
static let alertFollowCapKM: Double = 8 * 1.60934
|
static let alertFollowCapKM: Double = 8 * FuelSavingsCalculator.kilometresPerMile
|
||||||
|
|
||||||
/// The effective alert radius: the manual radius, or — when alerts follow
|
/// The effective alert radius: the manual radius, or — when alerts follow
|
||||||
/// the Stations-tab search — that distance (miles, converted via the
|
/// the Stations-tab search — that distance (miles, converted via the
|
||||||
@@ -1618,10 +1618,22 @@ struct FuelStore {
|
|||||||
// what the rows show.
|
// what the rows show.
|
||||||
if accurateDistancesEnabled,
|
if accurateDistancesEnabled,
|
||||||
accurateDistancesReady(userLat: userLat, userLng: userLng),
|
accurateDistancesReady(userLat: userLat, userLng: userLng),
|
||||||
let meters = roadDistanceMeters(for: station, userLat: userLat, userLng: userLng) {
|
let entry = roadDistanceEntry(for: station, userLat: userLat, userLng: userLng) {
|
||||||
return StationDistanceDisplay(km: meters / 1000.0, isApproximate: false)
|
let returnKM = (entry.meters + (entry.returnMeters ?? entry.meters)) / 1000.0
|
||||||
|
return StationDistanceDisplay(
|
||||||
|
km: entry.meters / 1000.0,
|
||||||
|
isApproximate: false,
|
||||||
|
returnKM: returnKM,
|
||||||
|
returnIsApproximate: entry.returnMeters == nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return StationDistanceDisplay(km: station.distanceKM(to: userLat, lng2: userLng), isApproximate: true)
|
let km = station.distanceKM(to: userLat, lng2: userLng)
|
||||||
|
return StationDistanceDisplay(
|
||||||
|
km: km,
|
||||||
|
isApproximate: true,
|
||||||
|
returnKM: km * 2,
|
||||||
|
returnIsApproximate: true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Radius test on the SAME distance the UI shows. Filtering straight-line
|
/// Radius test on the SAME distance the UI shows. Filtering straight-line
|
||||||
@@ -1710,35 +1722,50 @@ struct RoadDistanceDiagnostics: Equatable {
|
|||||||
/// road distance or the straight-line fallback. The confidence travels WITH the
|
/// road distance or the straight-line fallback. The confidence travels WITH the
|
||||||
/// value, so a label can never claim a confidence the figure doesn't have.
|
/// value, so a label can never claim a confidence the figure doesn't have.
|
||||||
struct StationDistanceDisplay: Equatable {
|
struct StationDistanceDisplay: Equatable {
|
||||||
let km: Double
|
let km: Double
|
||||||
let isApproximate: Bool
|
let isApproximate: Bool
|
||||||
|
let returnKM: Double
|
||||||
|
let returnIsApproximate: Bool
|
||||||
|
|
||||||
/// Row/sheet label: a straight-line fallback is marked "Approx." so a figure
|
init(km: Double,
|
||||||
/// that is NOT a routed road distance can never be read as one, and a cached
|
isApproximate: Bool,
|
||||||
/// road distance stays a bare figure. It updates by itself once routing lands,
|
returnKM: Double? = nil,
|
||||||
/// because `FuelStore.stationDistanceDisplay` re-reads the cache every call.
|
returnIsApproximate: Bool? = nil) {
|
||||||
func label(unit: DistanceUnit) -> String {
|
self.km = km
|
||||||
let text = unit.format(km)
|
self.isApproximate = isApproximate
|
||||||
return isApproximate ? "Approx. \(text)" : text
|
self.returnKM = returnKM ?? km * 2
|
||||||
}
|
self.returnIsApproximate = returnIsApproximate ?? isApproximate
|
||||||
|
}
|
||||||
|
|
||||||
/// Row/sheet label that keeps the one-way station distance primary, then
|
/// Row/sheet label: a straight-line fallback is marked "Approx." so a figure
|
||||||
/// adds the out-and-back distance in brackets. This matches Best Value's
|
/// that is NOT a routed road distance can never be read as one, and a cached
|
||||||
/// "extra return travel" basis without changing the actual distance model.
|
/// road distance stays a bare figure. It updates by itself once routing lands,
|
||||||
func labelWithReturn(unit: DistanceUnit) -> String {
|
/// because `FuelStore.stationDistanceDisplay` re-reads the cache every call.
|
||||||
"\(label(unit: unit)) (\(unit.format(km * 2)) return)"
|
func label(unit: DistanceUnit) -> String {
|
||||||
}
|
let text = unit.format(km)
|
||||||
|
return isApproximate ? "Approx. \(text)" : text
|
||||||
|
}
|
||||||
|
|
||||||
/// VoiceOver form of `label(unit:)`.
|
/// Row/sheet label that keeps the one-way station distance primary, then
|
||||||
func spokenLabel(unit: DistanceUnit) -> String {
|
/// adds the independently measured or estimated out-and-back distance.
|
||||||
let text = unit.spoken(km)
|
func labelWithReturn(unit: DistanceUnit) -> String {
|
||||||
return isApproximate ? "Approximately \(text)" : text
|
let returnText = unit.format(returnKM)
|
||||||
}
|
let returnLabel = returnIsApproximate ? "Approx. \(returnText)" : returnText
|
||||||
|
return "\(label(unit: unit)) (\(returnLabel) return)"
|
||||||
|
}
|
||||||
|
|
||||||
/// VoiceOver form of `labelWithReturn(unit:)`.
|
/// VoiceOver form of `label(unit:)`.
|
||||||
func spokenLabelWithReturn(unit: DistanceUnit) -> String {
|
func spokenLabel(unit: DistanceUnit) -> String {
|
||||||
"\(spokenLabel(unit: unit)), \(unit.spoken(km * 2)) return"
|
let text = unit.spoken(km)
|
||||||
}
|
return isApproximate ? "Approximately \(text)" : text
|
||||||
|
}
|
||||||
|
|
||||||
|
/// VoiceOver form of `labelWithReturn(unit:)`.
|
||||||
|
func spokenLabelWithReturn(unit: DistanceUnit) -> String {
|
||||||
|
let returnText = unit.spoken(returnKM)
|
||||||
|
let returnLabel = returnIsApproximate ? "Approximately \(returnText)" : returnText
|
||||||
|
return "\(spokenLabel(unit: unit)), \(returnLabel) return"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cached Apple-Maps road distances for nearby stations (see
|
/// Cached Apple-Maps road distances for nearby stations (see
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ enum SiriCheapestLookup {
|
|||||||
lng: Double,
|
lng: Double,
|
||||||
withinMiles: Double
|
withinMiles: Double
|
||||||
) -> FuelStation? {
|
) -> FuelStation? {
|
||||||
let radiusKM = withinMiles * 1.60934
|
let radiusKM = withinMiles * FuelSavingsCalculator.kilometresPerMile
|
||||||
// Same radius rule as the app list: the distance the answer would SHOW
|
// Same radius rule as the app list: the distance the answer would SHOW
|
||||||
// (cached road route where there is one).
|
// (cached road route where there is one).
|
||||||
return stations
|
return stations
|
||||||
|
|||||||
Reference in New Issue
Block a user