Files
fuelboard/FuelBoard/StationDetailView.swift
T
FuelBoard Contributor d348157a45 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.
2026-10-11 20:11:57 +01:00

136 lines
6.4 KiB
Swift

import SwiftUI
import MapKit
import UIKit
/// Detail sheet for a tapped station (when Settings → Tap station = More info).
/// Shows a small map preview, the address, distance from the current location,
/// and a Directions button that opens Apple Maps — the same destination a direct
/// tap would have opened. The station name lives in the sheet header, not in a
/// row.
struct StationDetailView: View {
let station: FuelStation
let location: Coordinate?
let distanceUnit: DistanceUnit
var fuelSavings: FuelSavingsAvailability? = nil
var fuelSavingsAmount: FuelSavingsFillAmount = FuelSavingsSettings.defaultPreferredAmount
var savingsBaselineStationName: String? = nil
/// Whether this station is starred for the fuel currently being viewed, and
/// the toggle that flips it. Passed from the list that opened the sheet, so
/// the star here and the star on the row are the same fuel-scoped
/// favourite and cannot drift apart.
var isFavourite: Bool = false
var onToggleFavourite: (() -> Void)? = nil
@Environment(\.dismiss) private var dismiss
@State private var showingReportIssueConfirmation = false
private let reportIssueURL = URL(string: "https://www.gov.uk/guidance/report-an-error-in-fuel-prices-or-forecourt-details")!
private var stationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(latitude: station.lat, longitude: station.lng)
}
var body: some View {
NavigationStack {
List {
Section {
Map(position: .constant(.region(MKCoordinateRegion(
center: stationCoordinate,
span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
)))) {
Marker(station.name, coordinate: stationCoordinate)
.tint(.red)
}
.frame(height: 180)
.clipShape(RoundedRectangle(cornerRadius: 12))
.allowsHitTesting(false)
.listRowInsets(EdgeInsets())
}
Section {
// The station name is NOT repeated here: the sheet header
// (navigationTitle, inline) already shows it, and the address
// line below has any leading repeat of the name stripped, so
// a "Name" row only said it twice.
LabeledContent("Address", value: StationAddressLine.display(address: station.address,
postcode: station.postcode,
name: station.name))
if let location {
let km = FuelStore.displayDistanceKM(
station: station, userLat: location.lat, userLng: location.lng
)
LabeledContent("Distance", value: distanceUnit.format(km))
.monospacedDigit()
}
ForEach(StationNotice.notices(for: station)) { notice in
Label(notice.title, systemImage: notice.symbolName)
.font(.subheadline.weight(.semibold))
.foregroundStyle(.orange)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(Capsule().fill(Color.orange.opacity(0.14)))
}
}
// Only shown while Fuel Savings backs it. `fuelSavings` is nil
// when the feature is off — `savingsLookup` returns nil when it
// is not configured — so the heading cannot be left dangling
// over an empty section.
if let fuelSavings {
Section {
FuelSavingsSummaryView(
availability: fuelSavings,
amount: fuelSavingsAmount,
baselineStationName: savingsBaselineStationName,
distanceUnit: distanceUnit
)
} header: {
Text("Best Value")
}
}
Section {
Button {
if let url = station.mapsDirectionsURL {
UIApplication.shared.open(url)
}
} label: {
Label("Directions", systemImage: "arrow.triangle.turn.up.right.diamond.fill")
}
Button(role: .destructive) {
showingReportIssueConfirmation = true
} label: {
Label("Report issue", systemImage: "exclamationmark.bubble.fill")
}
}
}
.navigationTitle(station.name)
.navigationBarTitleDisplayMode(.inline)
.alert("Report an error?", isPresented: $showingReportIssueConfirmation) {
Button("Cancel", role: .cancel) { }
Button("Report") {
UIApplication.shared.open(reportIssueURL)
}
} message: {
Text("Are you sure you want to report an error in fuel prices or forecourt details? This will open in your chosen browser.")
}
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Done") { dismiss() }
}
ToolbarItem(placement: .confirmationAction) {
Button {
onToggleFavourite?()
} label: {
Image(systemName: isFavourite ? "star.fill" : "star")
}
.tint(isFavourite ? Color.yellow : nil)
.disabled(onToggleFavourite == nil)
.accessibilityLabel(
isFavourite
? String(localized: "Remove from favourites", comment: "Unstar a station from the More info sheet")
: String(localized: "Add to favourites", comment: "Star a station from the More info sheet")
)
}
}
}
}
}