import SwiftUI /// 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 var onToggleFavourite: (FuelStation) -> Void = { _ in } /// 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[activeFuel] != nil } return available.sorted { lhs, rhs in 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) < rhs.distanceKM(to: location.lat, lng2: location.lng) } } private var cheapest: FuelStation? { ranked.first } private var cheapestPrice: Double? { cheapest?.prices[activeFuel] } init(favourites: [FuelStation], selectedFuel: FuelType, location: Coordinate?, distanceUnit: DistanceUnit, favouriteIDs: Set, 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 { List { if favourites.isEmpty { Section { VStack(spacing: 10) { Image(systemName: "star") .font(.system(size: 40)) .foregroundStyle(.secondary) Text("No favourites yet") .font(.headline) Text("Tap the star on any station in the Stations tab to pin it here, ranked by which is cheapest.") .font(.caption) .foregroundStyle(.secondary) .multilineTextAlignment(.center) } .frame(maxWidth: .infinity) .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 \(activeFuel.displayName) favourite: \(cheapest.name) — \(String(format: "%.1fp", price))") .font(.footnote) } } } Section("\(activeFuel.displayName) favourites — cheapest first") { ForEach(Array(ranked.enumerated()), id: \.element.id) { index, station in StationRow( station: station, fuel: activeFuel, location: location, distanceUnit: distanceUnit, baselinePrice: cheapestPrice, isTopResult: index == 0, isFavourite: favouriteIDs.contains(station.id), onToggleFavourite: { onToggleFavourite(station) } ) } } if let cheapest = cheapest { Section { 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) } } } } .navigationTitle("Favourites") } } }