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. /// Favourites are fuel-scoped entries — starring a station only pins it for /// the fuel you were viewing, so an Unleaded favourite never shows as Diesel. struct FavouritesView: View { let favourites: [FavouriteEntry] /// The app's currently selected fuel — used only as the initial tab. let selectedFuel: FuelType let location: Coordinate? let distanceUnit: DistanceUnit var onToggleFavourite: (FuelStation, FuelType) -> Void = { _, _ in } /// Fuel types that currently have at least one favourite — these are the /// only tabs shown (a fuel with no favourites gets no tab). private var availableFuels: [FuelType] { FuelType.allCases.filter { fuel in favourites.contains { $0.fuel == fuel } } } /// 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) } /// Stations favourited for the active fuel, cheapest first (distance tiebreak). private var ranked: [FuelStation] { let available = favourites.filter { $0.fuel == activeFuel }.map(\.station) 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) } } /// IDs favourited for the active fuel — the star state on each row. private var activeFuelFavouriteIDs: Set { Set(favourites.filter { $0.fuel == activeFuel }.map(\.station.id)) } private var cheapest: FuelStation? { ranked.first } private var cheapestPrice: Double? { cheapest?.prices[activeFuel] } init(favourites: [FavouriteEntry], selectedFuel: FuelType, location: Coordinate?, distanceUnit: DistanceUnit, onToggleFavourite: @escaping (FuelStation, FuelType) -> Void = { _, _ in }) { self.favourites = favourites self.selectedFuel = selectedFuel self.location = location self.distanceUnit = distanceUnit 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 for that fuel.") .font(.caption) .foregroundStyle(.secondary) .multilineTextAlignment(.center) } .frame(maxWidth: .infinity) .padding(.vertical, 24) } } else { Section("Fuel type") { FuelTypeSegmentedPicker( selection: $fuel, fuels: availableFuels ) } 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: activeFuelFavouriteIDs.contains(station.id), onToggleFavourite: { onToggleFavourite(station, activeFuel) } ) } } 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") } } }