Best Value: rename the remaining user-facing copy, hide the sheet section when off
Following the sort-segment rename, the rest of the user-facing "Worth the Drive"
copy moves to "Best Value".
- Settings: the toggle is now just "Best Value" (was "Enable Worth the Drive"),
and the narrative footer opens "Best Value estimates fuel cost only, …".
- More info sheet: the section header is "Best Value" (was "Worth the drive?"),
and the section itself is now conditional. `fuelSavings` is nil whenever the
feature is off — `savingsLookup` returns nil when it is not configured — so
the heading is never left dangling over an empty section while the feature is
disabled.
- The unavailable text reads "Best Value unavailable: …".
- Comments and doc comments that named the sort were updated so they match the
UI instead of contradicting it.
Deliberately unchanged: the enum's raw value stays `worthTheDrive`, so stored
sort preferences and widget configs keep decoding, and FuelSavingsStatus keeps
its verdict wording ("Worth the drive" / "Not worth the drive") — that describes
a station's verdict rather than the feature, and is not shown in the UI.
Verified on the simulator: with the feature OFF the more info sheet shows
Address / Distance / Directions and no heading at all; with it ON the sheet
shows the "Best Value" section; Settings reads "Fuel Savings" / "Best Value"
with no "Enable" anywhere. 172 tests, 0 failures.
This commit is contained in:
@@ -340,7 +340,7 @@ struct ContentView: View {
|
|||||||
private static var lastDriveLogLine: String?
|
private static var lastDriveLogLine: String?
|
||||||
|
|
||||||
/// One summary line per ranking pass (FB-DRIVE) so a device console shows
|
/// One summary line per ranking pass (FB-DRIVE) so a device console shows
|
||||||
/// whether the Worth the Drive list is actually carrying savings for the
|
/// whether the Best Value list is actually carrying savings for the
|
||||||
/// whole pool — the regression was silent otherwise.
|
/// whole pool — the regression was silent otherwise.
|
||||||
private func logWorthTheDriveSummary(_ ordered: [FuelStation],
|
private func logWorthTheDriveSummary(_ ordered: [FuelStation],
|
||||||
baseline: FuelStation?,
|
baseline: FuelStation?,
|
||||||
@@ -1357,7 +1357,7 @@ struct StationRow: View {
|
|||||||
VStack(alignment: .trailing, spacing: 2) {
|
VStack(alignment: .trailing, spacing: 2) {
|
||||||
if isTopResult {
|
if isTopResult {
|
||||||
// The badge always marks the CHEAPEST PUMP PRICE (never
|
// The badge always marks the CHEAPEST PUMP PRICE (never
|
||||||
// "best saving"), so in Worth the Drive order — where the
|
// "best saving"), so in Best Value order — where the
|
||||||
// top row is the best net saving — it may sit lower down
|
// top row is the best net saving — it may sit lower down
|
||||||
// the list, and says so. It sits ABOVE the price it
|
// the list, and says so. It sits ABOVE the price it
|
||||||
// annotates rather than beside the name, so it reads as
|
// annotates rather than beside the name, so it reads as
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ struct FuelSavingsSummaryView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .unavailable(let reason):
|
case .unavailable(let reason):
|
||||||
Text(compact ? "Savings unavailable" : "Worth the Drive unavailable: \(reason)")
|
Text(compact ? "Savings unavailable" : "Best Value unavailable: \(reason)")
|
||||||
.font(compact ? .caption2 : .caption)
|
.font(compact ? .caption2 : .caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
case nil:
|
case nil:
|
||||||
|
|||||||
@@ -150,13 +150,13 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Toggle("Enable Worth the Drive", isOn: fuelSavingsEnabledBinding)
|
Toggle("Best Value", isOn: fuelSavingsEnabledBinding)
|
||||||
// The feature's own controls follow its toggle: switched
|
// The feature's own controls follow its toggle: switched
|
||||||
// off they grey out but stay on screen (same rule as the
|
// off they grey out but stay on screen (same rule as the
|
||||||
// distance picker in Closest mode) and keep their values, so
|
// distance picker in Closest mode) and keep their values, so
|
||||||
// re-enabling restores exactly what was there. The sections
|
// re-enabling restores exactly what was there. The sections
|
||||||
// that USE the feature — the in-list fill-up control and the
|
// that USE the feature — the in-list fill-up control and the
|
||||||
// Worth the Drive sort option — are gated on `isConfigured`,
|
// Best Value sort option — are gated on `isConfigured`,
|
||||||
// which already includes `enabled`, so they hide entirely.
|
// which already includes `enabled`, so they hide entirely.
|
||||||
Group {
|
Group {
|
||||||
HStack {
|
HStack {
|
||||||
@@ -199,7 +199,7 @@ struct SettingsView: View {
|
|||||||
} header: {
|
} header: {
|
||||||
Text("Fuel Savings")
|
Text("Fuel Savings")
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("Worth the Drive estimates fuel cost only, using UK imperial MPG, your typical purchase amount, fuel prices and Apple Maps road distances. Real-world average MPG is better than manufacturer figures; the fill-up amount is your usual purchase, not tank capacity.")
|
Text("Best Value estimates fuel cost only, using UK imperial MPG, your typical purchase amount, fuel prices and Apple Maps road distances. Real-world average MPG is better than manufacturer figures; the fill-up amount is your usual purchase, not tank capacity.")
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
|||||||
@@ -69,15 +69,21 @@ struct StationDetailView: View {
|
|||||||
.background(Capsule().fill(Color.orange.opacity(0.14)))
|
.background(Capsule().fill(Color.orange.opacity(0.14)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Section {
|
// Only shown while Fuel Savings backs it. `fuelSavings` is nil
|
||||||
FuelSavingsSummaryView(
|
// when the feature is off — `savingsLookup` returns nil when it
|
||||||
availability: fuelSavings,
|
// is not configured — so the heading cannot be left dangling
|
||||||
amount: fuelSavingsAmount,
|
// over an empty section.
|
||||||
baselineStationName: savingsBaselineStationName,
|
if let fuelSavings {
|
||||||
distanceUnit: distanceUnit
|
Section {
|
||||||
)
|
FuelSavingsSummaryView(
|
||||||
} header: {
|
availability: fuelSavings,
|
||||||
Text("Worth the drive?")
|
amount: fuelSavingsAmount,
|
||||||
|
baselineStationName: savingsBaselineStationName,
|
||||||
|
distanceUnit: distanceUnit
|
||||||
|
)
|
||||||
|
} header: {
|
||||||
|
Text("Best Value")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ final class SortModeNamesTests: XCTestCase {
|
|||||||
XCTAssertEqual(SortMode.allCases.map(\.rawValue), ["cheapest", "closest", "worthTheDrive"])
|
XCTAssertEqual(SortMode.allCases.map(\.rawValue), ["cheapest", "closest", "worthTheDrive"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTheFeatureItselfIsStillCalledWorthTheDrive() {
|
func testTheVerdictWordingIsUnchanged() {
|
||||||
// Only the sort was renamed — the status wording the feature produces
|
// The feature and its sort are both called "Best Value" in user-facing
|
||||||
// keeps its own name.
|
// copy now (Settings toggle + narrative, the sheet's section header).
|
||||||
|
// FuelSavingsStatus's own wording describes a VERDICT rather than the
|
||||||
|
// feature, and is not shown in the UI, so it keeps the drive phrasing.
|
||||||
XCTAssertEqual(FuelSavingsStatus.worthTheDrive.displayName, "Worth the drive")
|
XCTAssertEqual(FuelSavingsStatus.worthTheDrive.displayName, "Worth the drive")
|
||||||
XCTAssertEqual(FuelSavingsStatus.notWorthTheDrive.displayName, "Not worth the drive")
|
XCTAssertEqual(FuelSavingsStatus.notWorthTheDrive.displayName, "Not worth the drive")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -398,7 +398,7 @@ enum FuelSavingsEstimator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ranks a whole pool for the Worth the Drive sort.
|
/// Ranks a whole pool for the Best Value sort.
|
||||||
///
|
///
|
||||||
/// EVERY priced station in the pool gets an estimate, not just the handful
|
/// EVERY priced station in the pool gets an estimate, not just the handful
|
||||||
/// whose road routes happen to have been measured: journeys that have no
|
/// whose road routes happen to have been measured: journeys that have no
|
||||||
|
|||||||
@@ -53,10 +53,10 @@ enum SortMode: String, Codable, CaseIterable, Identifiable {
|
|||||||
switch self {
|
switch self {
|
||||||
case .cheapest: return "Cheapest"
|
case .cheapest: return "Cheapest"
|
||||||
case .closest: return "Closest"
|
case .closest: return "Closest"
|
||||||
// "Best Value" is the user-facing name of this sort. The raw value stays
|
// "Best Value" is the user-facing name of this sort — the segmented
|
||||||
// `worthTheDrive` so stored preferences keep decoding, and it is the SORT
|
// control, the Stations caption, the Settings toggle and narrative, and
|
||||||
// that is called Best Value — the Fuel Savings feature itself is still
|
// the sheet's section header all read it. The raw value stays
|
||||||
// "Worth the Drive" (its Settings toggle, section and sheet header).
|
// `worthTheDrive` so stored preferences and widget configs keep decoding.
|
||||||
case .worthTheDrive: return "Best Value"
|
case .worthTheDrive: return "Best Value"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user