Best Value: explain price age per surface instead of one blanket warning

- Station detail now separates the station's own report age from the
  selected fuel's update time, so a recent station-wide report no longer
  reads as an old price.
- Warning copy names the fuel and says which side is weak: the station's
  price is not individually dated, or the baseline's is. The misleading
  "unverified or older than 7 days" line is gone.
- Wording stays "Closest" throughout; new copy uses Unleaded / Premium /
  Diesel rather than the internal grade codes.
- Localisation: new and updated keys for the added copy.
This commit is contained in:
FuelBoard Contributor
2026-10-15 21:37:35 +01:00
parent 7c2eeb2604
commit 9e77ee2947
8 changed files with 222 additions and 78 deletions
+36 -9
View File
@@ -2,7 +2,8 @@ import SwiftUI
struct FuelSavingsAdjustmentView: View {
@Binding var amount: FuelSavingsFillAmount
let referencePricePencePerLitre: Double?
@State private var unitChangeNotice: String?
private var range: ClosedRange<Double> {
FuelSavingsCalculator.fillAmountSliderRange(for: amount.unit)
@@ -44,6 +45,14 @@ struct FuelSavingsAdjustmentView: View {
Text("Changes the estimate for this session only — your Settings default stays unchanged.")
.font(.caption2)
.foregroundStyle(.secondary)
Text("£ compares the litres your budget buys at each station; Litres compares one fixed quantity.")
.font(.caption2)
.foregroundStyle(.secondary)
if let unitChangeNotice {
Text(unitChangeNotice)
.font(.caption2)
.foregroundStyle(.orange)
}
}
}
@@ -52,12 +61,10 @@ struct FuelSavingsAdjustmentView: View {
get: { amount.unit },
set: { newUnit in
guard newUnit != amount.unit else { return }
if let referencePricePencePerLitre,
let converted = try? FuelSavingsCalculator.convert(amount: amount, to: newUnit, referencePricePencePerLitre: referencePricePencePerLitre) {
amount = converted
} else {
amount = FuelSavingsFillAmount(value: newUnit == .pounds ? 40 : 30, unit: newUnit)
}
// No one litre figure equals a budget at every candidate price.
// A unit change starts a new, explicitly labelled comparison.
amount = FuelSavingsCalculator.newComparisonAmount(afterSwitching: amount, to: newUnit)
unitChangeNotice = "New comparison basis — adjust the \(newUnit.displayName) amount to your intended fill-up."
}
)
}
@@ -73,6 +80,9 @@ struct FuelSavingsAdjustmentView: View {
struct FuelSavingsSummaryView: View {
let availability: FuelSavingsAvailability?
let amount: FuelSavingsFillAmount
/// Candidate/baseline price confidence for every Best Value result.
var priceConfidence: FuelPriceFreshness.ComparisonConfidence = .ineligible
var priceWarning: String? = nil
var baselineStationName: String? = nil
var compact: Bool = false
/// Sizes the "extra return travel" caption in the user's own unit (the
@@ -107,6 +117,12 @@ struct FuelSavingsSummaryView: View {
.layoutPriority(1)
.accessibilityLabel("Comparison: \(comparisonLabel(for: result))")
}
if let warning = priceWarning ?? priceConfidence.warningText {
Text(warning)
.font(.caption2)
.foregroundStyle(.orange)
.accessibilityLabel(warning)
}
if let extraTravel = FuelSavingsFormat.extraTravelLabel(
extraDistanceMiles: result.extraDistanceMiles,
unit: distanceUnit,
@@ -143,6 +159,17 @@ struct FuelSavingsSummaryView: View {
LabeledContent("Your fill-up", value: amountText)
LabeledContent("Fuel you'll receive", value: String(format: "%.2f litres", result.fillLitres))
.monospacedDigit()
if amount.unit == .pounds {
Text("You pay the selected budget here. The saving compares the same litres at your closest eligible station; litres differ between stations, and this is not a refund.")
.font(.caption2)
.foregroundStyle(.secondary)
}
if let warning = priceWarning ?? priceConfidence.warningText {
Text(warning)
.font(.caption2)
.foregroundStyle(.orange)
.accessibilityLabel(warning)
}
if let extraTravel = FuelSavingsFormat.extraTravelLabel(
extraDistanceMiles: result.extraDistanceMiles,
unit: distanceUnit,
@@ -158,7 +185,7 @@ struct FuelSavingsSummaryView: View {
.font(.caption)
.foregroundStyle(.secondary)
} else {
Text("Compared with \(baselineStationName ?? "your closest station").")
Text(baselineStationName.map { "Compared with \($0)." } ?? "Compared with your closest eligible station.")
.font(.caption)
.foregroundStyle(.secondary)
LabeledContent(
@@ -233,7 +260,7 @@ struct FuelSavingsSummaryView: View {
}
/// VoiceOver form of the verdict. `spokenHeadline()` spells "Approx." out for
/// estimated comparisons and leaves the plain words ("Break even", "Closest
/// and leaves the plain words ("Break even", "Closest eligible
/// station") alone — one source, so visible and spoken copy cannot drift.
private func accessibilityHeadline(for result: FuelSavingsResult) -> String {
result.spokenHeadline()