Sort toggle (cheapest/closest) + RAG value rating per station with TOP badge

This commit is contained in:
FuelBoard Contributor
2026-08-11 14:14:07 +01:00
parent 543212a721
commit d9ec2f8c35
3 changed files with 147 additions and 15 deletions
+84 -11
View File
@@ -7,6 +7,7 @@ struct ContentView: View {
@State private var stations: [FuelStation] = []
@State private var selectedFuel: FuelType = FuelStore.loadSelectedFuel()
@State private var sortMode: SortMode = FuelStore.loadSortMode()
@State private var location: Coordinate? = {
if let loc = FuelStore.loadLocation() { return Coordinate(lat: loc.lat, lng: loc.lng) }
return nil
@@ -15,10 +16,24 @@ struct ContentView: View {
@State private var statusMessage = ""
@State private var locationManager = LocationManager()
private var cheapestPrice: Double? {
stations.compactMap { $0.prices[selectedFuel] }.min()
}
private var sortedStations: [FuelStation] {
stations
.filter { $0.prices[selectedFuel] != nil }
.sorted { lhs, rhs in
let available = stations.filter { $0.prices[selectedFuel] != nil }
switch sortMode {
case .closest:
guard let location else { return available.sorted { $0.prices[selectedFuel]! < $1.prices[selectedFuel]! } }
return available.sorted { lhs, rhs in
// Closest first; price only breaks ties.
let lDist = lhs.distanceKM(to: location.lat, lng2: location.lng)
let rDist = rhs.distanceKM(to: location.lat, lng2: location.lng)
if lDist != rDist { return lDist < rDist }
return lhs.prices[selectedFuel]! < rhs.prices[selectedFuel]!
}
case .cheapest:
return available.sorted { lhs, rhs in
// Cheapest first; distance only breaks ties.
let lPrice = lhs.prices[selectedFuel]!
let rPrice = rhs.prices[selectedFuel]!
@@ -27,17 +42,30 @@ struct ContentView: View {
return lhs.distanceKM(to: location.lat, lng2: location.lng) <
rhs.distanceKM(to: location.lat, lng2: location.lng)
}
}
}
var body: some View {
NavigationStack {
List {
Section {
Text("Cheapest \(selectedFuel.displayName) near you. Tap a station for directions.")
Text("\(sortMode == .closest ? "Closest" : "Cheapest") \(selectedFuel.displayName) — tap a station for directions. 🟢 great value · 🟡 okay · 🔴 pricey")
.font(.footnote)
.foregroundStyle(.secondary)
}
Section("Sort by") {
Picker("Sort by", selection: $sortMode) {
ForEach(SortMode.allCases) { mode in
Text(mode.displayName).tag(mode)
}
}
.pickerStyle(.segmented)
.onChange(of: sortMode) { _, newValue in
FuelStore.saveSortMode(newValue)
}
}
Section("Fuel type") {
Picker("Fuel type", selection: $selectedFuel) {
ForEach(FuelType.allCases) { fuel in
@@ -61,8 +89,14 @@ struct ContentView: View {
Text("No \(selectedFuel.displayName) stations found.")
.foregroundStyle(.secondary)
} else {
ForEach(sortedStations) { station in
StationRow(station: station, fuel: selectedFuel, location: location)
ForEach(Array(sortedStations.enumerated()), id: \.element.id) { index, station in
StationRow(
station: station,
fuel: selectedFuel,
location: location,
cheapestPrice: cheapestPrice,
isTopResult: index == 0
)
}
}
}
@@ -147,12 +181,40 @@ struct StationRow: View {
let station: FuelStation
let fuel: FuelType
let location: Coordinate?
let cheapestPrice: Double?
let isTopResult: Bool
private var ragColor: Color {
guard let price = station.prices[fuel], let cheapestPrice else { return .gray }
switch RAGRating.rating(price: price, cheapest: cheapestPrice) {
case .green: return .green
case .amber: return .orange
case .red: return .red
}
}
private var deltaText: String? {
guard let price = station.prices[fuel], let cheapestPrice else { return nil }
let delta = price - cheapestPrice
if delta <= 0.05 { return "best" }
return String(format: "+%.1fp", delta)
}
var body: some View {
HStack(spacing: 12) {
VStack(alignment: .leading, spacing: 2) {
Text(station.name)
.font(.headline)
HStack(spacing: 6) {
Text(station.name)
.font(.headline)
if isTopResult {
Text("TOP")
.font(.caption2.bold())
.padding(.horizontal, 5)
.padding(.vertical, 1)
.background(Capsule().fill(.blue.opacity(0.15)))
.foregroundStyle(.blue)
}
}
Text("\(station.address), \(station.postcode)")
.font(.caption)
.foregroundStyle(.secondary)
@@ -164,9 +226,20 @@ struct StationRow: View {
}
Spacer()
if let price = station.prices[fuel] {
Text(String(format: "%.1fp", price))
.font(.title3.bold())
.foregroundStyle(.green)
VStack(alignment: .trailing, spacing: 2) {
HStack(spacing: 5) {
Circle()
.fill(ragColor)
.frame(width: 10, height: 10)
Text(String(format: "%.1fp", price))
.font(.title3.bold())
}
if let deltaText {
Text(deltaText)
.font(.caption2.bold())
.foregroundStyle(ragColor)
}
}
}
Image(systemName: "arrow.triangle.turn.up.right.circle")
.foregroundStyle(.secondary)