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:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user