Favourites are fuel-scoped: starring pins the station for one fuel only

Fixing the bug where favouriting an Unleaded entry also created a Diesel
favourite. Favourites are now [FavouriteEntry] = (station, fuel) pairs:
- Starring a row pins it only for the fuel being viewed
- Favourites tabs show only fuels that actually have favourites
- Geofence monitor gives priority slots only to favourites matching the
  monitored fuel
- Tests updated + new fuel-scoping test (31/31 passing)
This commit is contained in:
FuelBoard Contributor
2026-08-12 08:31:08 +01:00
parent 0dbf88ee7d
commit 35e0adaf0b
6 changed files with 88 additions and 54 deletions
+19 -15
View File
@@ -3,20 +3,21 @@ 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: [FuelStation]
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 favouriteIDs: Set<String>
var onToggleFavourite: (FuelStation) -> Void = { _ in }
var onToggleFavourite: (FuelStation, FuelType) -> Void = { _, _ in }
/// Fuel types that currently have at least one favourite selling them
/// these are the only tabs shown.
/// 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.prices[fuel] != nil }
favourites.contains { $0.fuel == fuel }
}
}
@@ -28,9 +29,9 @@ struct FavouritesView: View {
availableFuels.contains(fuel) ? fuel : (availableFuels.first ?? .e10)
}
/// Favourites that sell the active fuel, cheapest first (distance tiebreak).
/// Stations favourited for the active fuel, cheapest first (distance tiebreak).
private var ranked: [FuelStation] {
let available = favourites.filter { $0.prices[activeFuel] != nil }
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]!
@@ -41,20 +42,23 @@ struct FavouritesView: View {
}
}
/// 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))
}
private var cheapest: FuelStation? { ranked.first }
private var cheapestPrice: Double? { cheapest?.prices[activeFuel] }
init(favourites: [FuelStation],
init(favourites: [FavouriteEntry],
selectedFuel: FuelType,
location: Coordinate?,
distanceUnit: DistanceUnit,
favouriteIDs: Set<String>,
onToggleFavourite: @escaping (FuelStation) -> Void = { _ in }) {
onToggleFavourite: @escaping (FuelStation, FuelType) -> Void = { _, _ in }) {
self.favourites = favourites
self.selectedFuel = selectedFuel
self.location = location
self.distanceUnit = distanceUnit
self.favouriteIDs = favouriteIDs
self.onToggleFavourite = onToggleFavourite
_fuel = State(initialValue: selectedFuel)
}
@@ -70,7 +74,7 @@ struct FavouritesView: View {
.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.")
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)
@@ -108,8 +112,8 @@ struct FavouritesView: View {
distanceUnit: distanceUnit,
baselinePrice: cheapestPrice,
isTopResult: index == 0,
isFavourite: favouriteIDs.contains(station.id),
onToggleFavourite: { onToggleFavourite(station) }
isFavourite: activeFuelFavouriteIDs.contains(station.id),
onToggleFavourite: { onToggleFavourite(station, activeFuel) }
)
}
}