From 7aa2485994dd4720f9bf899c58e3653a6a06f266 Mon Sep 17 00:00:00 2001 From: FuelBoard Contributor Date: Sat, 17 Oct 2026 20:24:21 +0100 Subject: [PATCH] Move detail approximation after verdict amount --- FuelBoard/FuelSavingsViews.swift | 2 +- FuelBoard/StationDetailSections.swift | 2 +- .../FuelSavingsEstimateTests.swift | 22 +++++++++++++++++++ Shared/FuelSavings.swift | 20 +++++++++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/FuelBoard/FuelSavingsViews.swift b/FuelBoard/FuelSavingsViews.swift index c7ab7dd..694335e 100644 --- a/FuelBoard/FuelSavingsViews.swift +++ b/FuelBoard/FuelSavingsViews.swift @@ -109,7 +109,7 @@ struct FuelSavingsCapsule: View { HStack(spacing: compact ? 3 : 5) { Image(systemName: Self.symbol(for: result.status)) .accessibilityHidden(true) - Text(result.capsuleHeadline()) + Text(compact ? result.capsuleHeadline() : result.detailCapsuleHeadline()) .lineLimit(1) // Measured on a 393pt screen: the capsule is handed ~101pt, of // which the icon, its spacing and the 8pt padding either side diff --git a/FuelBoard/StationDetailSections.swift b/FuelBoard/StationDetailSections.swift index 270a842..f1fc6c1 100644 --- a/FuelBoard/StationDetailSections.swift +++ b/FuelBoard/StationDetailSections.swift @@ -148,7 +148,7 @@ struct BestValueDetailCard: View { Spacer(minLength: 8) VStack(alignment: .trailing, spacing: 4) { FuelSavingsCapsule(result: result, compact: false) - .accessibilityLabel(result.spokenHeadline()) + .accessibilityLabel(result.detailCapsuleSpokenHeadline()) comparison(result) } } diff --git a/FuelBoardTests/Tests/FuelBoardSharedTests/FuelSavingsEstimateTests.swift b/FuelBoardTests/Tests/FuelBoardSharedTests/FuelSavingsEstimateTests.swift index c13b63c..f95b7a6 100644 --- a/FuelBoardTests/Tests/FuelBoardSharedTests/FuelSavingsEstimateTests.swift +++ b/FuelBoardTests/Tests/FuelBoardSharedTests/FuelSavingsEstimateTests.swift @@ -39,6 +39,28 @@ final class FuelSavingsEstimateTests: XCTestCase { XCTAssertEqual(result(.breakEven, .estimated).listingCapsuleHeadline(), "Break even (approx.)") } + func testDetailCapsulePlacesApproximationAfterTheFinancialResult() { + func result(_ status: FuelSavingsStatus, _ confidence: JourneyConfidence) -> FuelSavingsResult { + FuelSavingsResult( + fillLitres: 30, + grossSavingGBP: status == .notWorthTheDrive ? 0 : 2.50, + extraDrivingCostGBP: status == .notWorthTheDrive ? 1.20 : 0, + extraDistanceMiles: 2, + netSavingGBP: status == .notWorthTheDrive ? -1.20 : 2.50, + status: status, + journeyConfidence: confidence + ) + } + + XCTAssertEqual(result(.worthTheDrive, .estimated).detailCapsuleHeadline(), "Save £2.50 (approx.)") + XCTAssertEqual(result(.notWorthTheDrive, .estimated).detailCapsuleHeadline(), "Costs £1.20 more (approx.)") + XCTAssertEqual(result(.breakEven, .estimated).detailCapsuleHeadline(), "Break even (approx.)") + XCTAssertEqual(result(.worthTheDrive, .confirmed).detailCapsuleHeadline(), "Save £2.50") + XCTAssertEqual(result(.notWorthTheDrive, .confirmed).detailCapsuleHeadline(), "Costs £1.20 more") + XCTAssertEqual(result(.breakEven, .confirmed).detailCapsuleHeadline(), "Break even") + XCTAssertEqual(result(.worthTheDrive, .estimated).detailCapsuleSpokenHeadline(), "Save £2.50 (approximately)") + } + private func station(_ id: String, price: Double?, lat: Double = 51.5, diff --git a/Shared/FuelSavings.swift b/Shared/FuelSavings.swift index 496cfd4..082c14e 100644 --- a/Shared/FuelSavings.swift +++ b/Shared/FuelSavings.swift @@ -236,6 +236,26 @@ extension FuelSavingsResult { headline().replacingOccurrences(of: "Approx. ", with: "Approximately ") } + /// Full monetary verdict used by Station Details. Estimated confidence is a + /// trailing qualifier so the amount remains the first figure users scan. + func detailCapsuleHeadline() -> String { + let qualifier = journeyConfidence.isEstimated ? " (approx.)" : "" + switch status { + case .baseline: + return Self.baselineHeadline + case .breakEven: + return "\(Self.breakEvenHeadline)\(qualifier)" + case .worthTheDrive: + return "Save \(FuelSavingsFormat.money(netSavingGBP))\(qualifier)" + case .notWorthTheDrive: + return "Costs \(FuelSavingsFormat.money(abs(netSavingGBP))) more\(qualifier)" + } + } + + func detailCapsuleSpokenHeadline() -> String { + detailCapsuleHeadline().replacingOccurrences(of: "(approx.)", with: "(approximately)") + } + /// Row capsule copy. The baseline relationship is shown as secondary text /// beside the capsule in the station listing and detail summary, so the /// capsule itself contains only the verdict.