Files
fuelboard/FuelBoard/FavouritesView.swift
T

145 lines
6.4 KiB
Swift

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
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
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<String> {
Set(favourites.filter { $0.fuel == activeFuel }.map(\.station.id))
}
init(favourites: [FavouriteEntry],
selectedFuel: FuelType,
location: Coordinate?,
distanceUnit: DistanceUnit,
onToggleFavourite: @escaping (FuelStation, FuelType) -> Void = { _, _ in },
onReorder: @escaping ([FavouriteEntry]) -> Void = { _ in }) {
self.favourites = favourites
self.selectedFuel = selectedFuel
self.location = location
self.distanceUnit = distanceUnit
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,
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 {
EditButton()
}
}
}
}
}