import SwiftUI /// Favourites tab — starred stations, split by fuel type. Each fuel type /// with at least one favourite appears as its own tab. The list shows the /// USER'S manual order (drag to reorder — Edit button in the toolbar): the /// FIRST station in each fuel's list is the one used as "the favourite" in /// single widgets. 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 let priceDisplayStyle: PriceDisplayStyle var onToggleFavourite: (FuelStation, FuelType) -> Void = { _, _ in } /// Persists a reordered favourites array (after drag-and-drop). var onReorder: ([FavouriteEntry]) -> 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 /// Trends sheet (price history chart) presentation state. @State private var showTrends = false private var activeFuel: FuelType { availableFuels.contains(fuel) ? fuel : (availableFuels.first ?? .e10) } /// Stations favourited for the active fuel, in the USER'S stored order — /// NOT price-sorted. The first row is the favourite used by single /// widgets (see footer note). private var ordered: [FuelStation] { favourites.filter { $0.fuel == activeFuel }.map(\.station) } /// Cheapest favourited station for the active fuel — the RAG baseline and /// the "cheapest favourite" callout, independent of the manual order. private var cheapest: FuelStation? { ordered.min { ($0.prices[activeFuel] ?? .infinity) < ($1.prices[activeFuel] ?? .infinity) } } private var cheapestPrice: Double? { cheapest?.prices[activeFuel] } /// 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)) } init(favourites: [FavouriteEntry], selectedFuel: FuelType, location: Coordinate?, distanceUnit: DistanceUnit, priceDisplayStyle: PriceDisplayStyle, onToggleFavourite: @escaping (FuelStation, FuelType) -> Void = { _, _ in }, onReorder: @escaping ([FavouriteEntry]) -> Void = { _ in }) { self.favourites = favourites self.selectedFuel = selectedFuel self.location = location self.distanceUnit = distanceUnit self.priceDisplayStyle = priceDisplayStyle self.onToggleFavourite = onToggleFavourite self.onReorder = onReorder _fuel = State(initialValue: selectedFuel) } /// Drag-and-drop reorder (List edit mode): the active fuel's entries are /// reordered within the global array; the fuel's block keeps its original /// position and other fuels keep their relative order (FuelStore helper). private func moveFavourite(fromOffsets source: IndexSet, toOffset destination: Int) { onReorder(FuelStore.reorderedFavourites( favourites, fuel: activeFuel, fromOffsets: source, toOffset: destination )) } 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. The first station in each list becomes the favourite used in single widgets — drag to put your pick first.") .font(.caption) .foregroundStyle(.secondary) .multilineTextAlignment(.center) } .frame(maxWidth: .infinity) .padding(.vertical, 24) } } else { Section("Fuel type") { FuelTypeSegmentedPicker( selection: $fuel, fuels: availableFuels ) } Section { ForEach(Array(ordered.enumerated()), id: \.element.id) { index, station in StationRow( station: station, fuel: activeFuel, location: location, distanceUnit: distanceUnit, priceDisplayStyle: priceDisplayStyle, baselinePrice: cheapestPrice, isTopResult: index == 0, isFavourite: activeFuelFavouriteIDs.contains(station.id), onToggleFavourite: { onToggleFavourite(station, activeFuel) } ) } .onMove(perform: moveFavourite) } header: { Text("\(activeFuel.displayName) favourites") } footer: { Text("The first station is the favourite used in single widgets. Tap Edit, then drag to reorder.") } 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") .toolbar { if !favourites.isEmpty { ToolbarItemGroup(placement: .topBarTrailing) { Button { showTrends = true } label: { Image(systemName: "chart.xyaxis.line") .accessibilityLabel("Trends") } EditButton() } } } .sheet(isPresented: $showTrends) { TrendsView( favourites: favourites, selectedFuel: activeFuel, priceDisplayStyle: priceDisplayStyle ) } } } }