Favourites: split list by fuel type with segmented tabs

Only fuel types that currently have at least one favourite appear as
tabs; each tab ranks that fuel's favourites cheapest-first and the
narrative (crown header, section title, cheapest-favourite explainer)
names the active fuel.
This commit is contained in:
FuelBoard Contributor
2026-08-12 08:17:10 +01:00
parent 7903c9a2d8
commit 0dbf88ee7d
+53 -11
View File
@@ -1,21 +1,39 @@
import SwiftUI
/// Favourites tab starred stations, ranked cheapest-first for the selected
/// fuel, with the cheapest favourite called out at the top.
/// Favourites tab starred stations, split by fuel type. Each fuel type
/// with at least one favourite appears as its own tab; that tab's favourites
/// are ranked cheapest-first for that fuel, with the cheapest called out.
struct FavouritesView: View {
let favourites: [FuelStation]
/// The app's currently selected fuel used only as the initial tab.
let selectedFuel: FuelType
let location: Coordinate?
let distanceUnit: DistanceUnit
let favouriteIDs: Set<String>
var onToggleFavourite: (FuelStation) -> Void = { _ in }
/// Favourites that sell the selected fuel, cheapest first (distance tiebreak).
/// Fuel types that currently have at least one favourite selling them
/// these are the only tabs shown.
private var availableFuels: [FuelType] {
FuelType.allCases.filter { fuel in
favourites.contains { $0.prices[fuel] != nil }
}
}
/// The active tab. Falls back if the chosen fuel ever loses all its
/// favourites (e.g. the last one is un-starred while viewing it).
@State private var fuel: FuelType = .e10
private var activeFuel: FuelType {
availableFuels.contains(fuel) ? fuel : (availableFuels.first ?? .e10)
}
/// Favourites that sell the active fuel, cheapest first (distance tiebreak).
private var ranked: [FuelStation] {
let available = favourites.filter { $0.prices[selectedFuel] != nil }
let available = favourites.filter { $0.prices[activeFuel] != nil }
return available.sorted { lhs, rhs in
let lPrice = lhs.prices[selectedFuel]!
let rPrice = rhs.prices[selectedFuel]!
let lPrice = lhs.prices[activeFuel]!
let rPrice = rhs.prices[activeFuel]!
if lPrice != rPrice { return lPrice < rPrice }
guard let location else { return false }
return lhs.distanceKM(to: location.lat, lng2: location.lng) <
@@ -24,7 +42,22 @@ struct FavouritesView: View {
}
private var cheapest: FuelStation? { ranked.first }
private var cheapestPrice: Double? { cheapest?.prices[selectedFuel] }
private var cheapestPrice: Double? { cheapest?.prices[activeFuel] }
init(favourites: [FuelStation],
selectedFuel: FuelType,
location: Coordinate?,
distanceUnit: DistanceUnit,
favouriteIDs: Set<String>,
onToggleFavourite: @escaping (FuelStation) -> Void = { _ in }) {
self.favourites = favourites
self.selectedFuel = selectedFuel
self.location = location
self.distanceUnit = distanceUnit
self.favouriteIDs = favouriteIDs
self.onToggleFavourite = onToggleFavourite
_fuel = State(initialValue: selectedFuel)
}
var body: some View {
NavigationStack {
@@ -46,22 +79,31 @@ struct FavouritesView: View {
.padding(.vertical, 24)
}
} else {
Section("Fuel type") {
Picker("Fuel type", selection: $fuel) {
ForEach(availableFuels) { fuel in
Text(fuel.shortName).tag(fuel)
}
}
.pickerStyle(.segmented)
}
Section {
if let cheapest, let price = cheapestPrice {
HStack(spacing: 10) {
Image(systemName: "crown.fill")
.foregroundStyle(.yellow)
Text("Cheapest favourite: \(cheapest.name)\(String(format: "%.1fp", price))")
Text("Cheapest \(activeFuel.displayName) favourite: \(cheapest.name)\(String(format: "%.1fp", price))")
.font(.footnote)
}
}
}
Section("Favourites — cheapest first") {
Section("\(activeFuel.displayName) favourites — cheapest first") {
ForEach(Array(ranked.enumerated()), id: \.element.id) { index, station in
StationRow(
station: station,
fuel: selectedFuel,
fuel: activeFuel,
location: location,
distanceUnit: distanceUnit,
baselinePrice: cheapestPrice,
@@ -74,7 +116,7 @@ struct FavouritesView: View {
if let cheapest = cheapest {
Section {
Text("\(cheapest.name) is your cheapest favourite for \(selectedFuel.displayName). Favourites are monitored automatically — switch to the Alerts tab and enable alerts to get pinged when you approach one that's the cheapest within the radius.")
Text("\(cheapest.name) is your cheapest \(activeFuel.displayName) favourite. Favourites are monitored automatically — switch to the Alerts tab and enable alerts to get pinged when you approach one that's the cheapest within the radius.")
.font(.caption)
.foregroundStyle(.secondary)
}