Files
fuelboard/FuelBoard/StationDetailView.swift
T
FuelBoard Contributor d7a0ecb79a Add Worth the Drive fuel savings with road-routed estimates
Optional Fuel Savings feature (off by default): vehicle MPG in UK imperial gallons, fill amount in pounds or litres, persisted through the existing keychain/app-group store. Ranks the WHOLE station pool by estimated net saving (fuel-price saving at each pump minus the extra return-trip driving cost) using MapKit road distances with a straight-line fallback marked (est.).

The closest station with a valid price for the selected fuel is the comparison baseline and is labelled "Closest station"; other rows read "Save £X.XX after travel" / "£X.XX worse off after travel" / "Break even". Labels live in the shared model so they are unit-tested. The TOP badge still marks the cheapest pump price and reads "CHEAPEST" when the list is ranked by net saving.

Road routing now prioritises the Worth-the-Drive candidates plus the baseline and merges cache entries instead of replacing them, so far-out stations get real routes rather than permanent straight-line estimates. Numeric fields gain a Done button above the keypad (decimalPad has no Return key); the fill-up control is hidden unless the feature is configured; Favourites shares one session fill amount with the station list. 131 tests.
2026-10-11 14:34:53 +01:00

103 lines
4.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 station name, address, distance from the
/// current location, and a Directions button that opens Apple Maps — the same
/// destination a direct tap would have opened.
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
@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 {
LabeledContent("Name", value: station.name)
LabeledContent("Address", value: "\(station.address), \(station.postcode)")
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)))
}
}
Section {
FuelSavingsSummaryView(
availability: fuelSavings,
amount: fuelSavingsAmount,
baselineStationName: savingsBaselineStationName
)
} header: {
Text("Worth the drive?")
}
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() }
}
}
}
}
}