Add Best Value small widget and standardise Closest wording

This commit is contained in:
FuelBoard Contributor
2026-10-15 17:14:02 +01:00
parent a5688cbac2
commit 3819b8a6ab
13 changed files with 125 additions and 46 deletions
+60 -7
View File
@@ -95,7 +95,8 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
data: FuelPriceWidgetEntryData(
date: Date(), stations: Array(sample.prefix(8)),
fuel: .e10, sort: .cheapest, isFavourites: false,
location: nil, locationSource: "none", unit: unit
location: nil, locationSource: "none", unit: unit,
bestValueResult: nil
)
)
}
@@ -146,7 +147,8 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
// Per-widget config: fuel + sort + distance come from THIS widget instance.
let fuel = FuelType(rawValue: configuration.fuel.rawValue) ?? .e10
let isFavourites = configuration.sort == .favourites
let sort: SortMode = configuration.sort == .closest ? .closest : .cheapest
let sort: SortMode = configuration.sort == .closest ? .closest :
(configuration.sort == .worthTheDrive ? .worthTheDrive : .cheapest)
let radiusKM = Double(configuration.distance.miles) * FuelSavingsCalculator.kilometresPerMile
// 1) Try a fresh location fix (bounded to a few seconds). The fix's own
@@ -200,6 +202,7 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
// 3) Load + order stations per widget mode.
let ordered: [FuelStation]
var bestValueResult: FuelSavingsResult?
if isFavourites {
let favourites = FuelStore.loadFavourites()
.filter { $0.station.prices[$0.fuel] != nil }
@@ -265,7 +268,7 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
let filtered = stations.filter { $0.prices[fuel] != nil }
switch sort {
case .closest:
// Nearest first; price only breaks ties.
// Closest first; price only breaks ties.
ordered = filtered.sorted { lhs, rhs in
if let origin {
let lDist = FuelStore.displayDistanceKM(station: lhs, userLat: origin.lat, userLng: origin.lng)
@@ -274,9 +277,59 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
}
return lhs.prices[fuel]! < rhs.prices[fuel]!
}
case .cheapest, .worthTheDrive:
// Cheapest first; distance only breaks ties. Worth-the-Drive is app-only;
// widgets fall back to cheapest ordering if a stored config ever carries it.
case .worthTheDrive:
guard let origin else {
ordered = filtered.sorted { $0.prices[fuel]! < $1.prices[fuel]! }
break
}
let journey: (FuelStation) -> FuelSavingsJourney? = { station in
if let meters = FuelStore.roadJourneyMeters(for: station, userLat: origin.lat, userLng: origin.lng) {
let confidence: JourneyConfidence = FuelStore.hasMeasuredReturnRoadDistance(
for: station, userLat: origin.lat, userLng: origin.lng
) ? .confirmed : .estimated
return FuelSavingsJourney(
miles: meters / FuelSavingsCalculator.metresPerMile,
basis: .roadRoute,
confidence: confidence
)
}
return FuelSavingsJourney(
miles: FuelStore.displayDistanceKM(station: station, userLat: origin.lat, userLng: origin.lng) /
FuelSavingsCalculator.kilometresPerMile * 2,
basis: .straightLine,
confidence: .estimated
)
}
let settings = FuelStore.loadFuelSavingsSettings()
guard settings.isConfigured else {
ordered = filtered.sorted { $0.prices[fuel]! < $1.prices[fuel]! }
break
}
let amount = settings.preferredAmount
let baseline = FuelSavingsEstimator.baselineStation(filtered, fuel: fuel, journeyMiles: journey)
let ranked = FuelSavingsEstimator.ranked(
filtered,
fuel: fuel,
baseline: baseline,
settings: settings,
amount: amount,
journeyMiles: journey
)
ordered = ranked
if let first = ranked.first, let baseline {
let baselineJourney = journey(baseline)
let result = FuelSavingsEstimator.availability(
station: first,
fuel: fuel,
baseline: baseline,
settings: settings,
amount: amount,
journey: journey(first),
baselineJourney: baselineJourney
)
bestValueResult = result?.result
}
case .cheapest:
ordered = filtered.sorted { lhs, rhs in
let lPrice = lhs.prices[fuel]!
let rPrice = rhs.prices[fuel]!
@@ -295,7 +348,7 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
date: Date(), stations: Array(ordered.prefix(8)),
fuel: fuel, sort: sort, isFavourites: isFavourites,
location: origin, locationSource: source,
unit: unit
unit: unit, bestValueResult: bestValueResult
)
), originSource)
}
+13 -3
View File
@@ -30,6 +30,7 @@ enum WidgetFuel: String, AppEnum, CaseIterable, Codable {
enum WidgetSort: String, AppEnum, CaseIterable, Codable {
case cheapest
case closest
case worthTheDrive
case favourites
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Sort by"
@@ -37,6 +38,7 @@ enum WidgetSort: String, AppEnum, CaseIterable, Codable {
static var caseDisplayRepresentations: [WidgetSort: DisplayRepresentation] = [
.cheapest: "Cheapest",
.closest: "Closest",
.worthTheDrive: "Best Value",
.favourites: "Favourites",
]
}
@@ -112,9 +114,17 @@ struct FuelBoardWidgetConfigurationIntent: WidgetConfigurationIntent, WidgetConf
\.$sort
}
} otherwise: {
Summary("Show \(\.$fuel) by \(\.$sort)") {
\.$fuel
\.$sort
When(\.$sort, .equalTo, WidgetSort.worthTheDrive) {
Summary("Show \(\.$fuel) by \(\.$sort) within \(\.$distance)") {
\.$fuel
\.$sort
\.$distance
}
} otherwise: {
Summary("Show \(\.$fuel) by \(\.$sort)") {
\.$fuel
\.$sort
}
}
}
}