Retire the Best Value proximity alert option; move Key sheet copy into a tested model

- Alerts: drop the alert-type picker and the Best Value alert path, so
  proximity alerts always follow the cheapest station. A stored `bestValue`
  selection now falls back to cheapest when loaded.
- Key sheet: the price-rating, savings, station-label and favourite wording
  comes from `KeySheetContent` in Shared/FuelSavings.swift — a Foundation-only
  model with tests that pin the exact meaning of each indicator — and the
  sheet is built from that model rather than inline strings.
- BACKLOG: record the plan to restore Best Value proximity alerts behind one
  shared evaluator (user-location baseline, a single disclosed qualification
  rule, and real/Test alert parity).
- Bump CURRENT_PROJECT_VERSION to 29.
This commit is contained in:
FuelBoard Contributor
2026-10-18 17:16:12 +01:00
parent c331f3d663
commit 6cbb083208
11 changed files with 355 additions and 225 deletions
+4 -40
View File
@@ -37,7 +37,6 @@ struct ContentView: View {
@State private var alertsEnabled: Bool = FuelStore.loadAlertsEnabled()
@State private var alertsRadius: Double = FuelStore.loadAlertsRadius()
@State private var alertsFuel: FuelType = FuelStore.loadAlertsFuel()
@State private var alertsMode: AlertMode = FuelStore.loadAlertsMode()
@State private var alertsBestValueThresholdGBP: Double = FuelStore.loadAlertsBestValueThresholdGBP()
@State private var favouriteDropAlertsEnabled: Bool = FuelStore.loadFavouriteDropAlertsEnabled()
@State private var favouriteDropAlertsFuel: FuelType = FuelStore.loadFavouriteDropAlertsFuel()
@@ -152,23 +151,13 @@ struct ContentView: View {
FuelStore.effectiveAlertsRadiusKM(followsSearch: alertsFollowsSearch, manualKM: alertsRadius)
}
private var effectiveAlertsMode: AlertMode {
(alertsMode == .bestValue && !fuelSavingsSettings.isConfigured) ? .cheapest : alertsMode
}
private func ensureBestValueAlertModeIsAvailable() {
guard alertsMode == .bestValue, !fuelSavingsSettings.isConfigured else { return }
alertsMode = .cheapest
FuelStore.saveAlertsMode(.cheapest)
}
private func updateAlertMonitor(fuel overrideFuel: FuelType? = nil) {
monitor.update(
stations: stations,
favourites: refreshedFavourites,
fuel: overrideFuel ?? alertsFuel,
radiusKM: effectiveAlertsRadiusKM,
alertMode: effectiveAlertsMode,
alertMode: .cheapest,
bestValueThresholdGBP: alertsBestValueThresholdGBP,
fuelSavingsSettings: fuelSavingsSettings,
fuelSavingsAmount: sessionFuelSavingsAmount
@@ -1154,7 +1143,6 @@ struct ContentView: View {
// or the choice wouldn't take effect until the next launch.
fuelSavingsSettings = FuelStore.loadFuelSavingsSettings()
sessionFuelSavingsAmount = fuelSavingsSettings.preferredAmount
ensureBestValueAlertModeIsAvailable()
locationManager.startForegroundTracking()
installLocationUpdateHook()
ensureBackgroundMotionTrackingIfNeeded()
@@ -1174,7 +1162,6 @@ struct ContentView: View {
#if DEBUG
print("FB-SAVINGS settings changed: enabled=\(old.enabled)->\(new.enabled) configured=\(new.isConfigured)")
#endif
ensureBestValueAlertModeIsAvailable()
updateAlertMonitor()
}
.onChange(of: sessionFuelSavingsAmount) { _, _ in
@@ -1257,16 +1244,6 @@ struct ContentView: View {
FuelStore.saveAlertsFuel(newValue)
updateAlertMonitor(fuel: newValue)
}
.onChange(of: alertsMode) { _, newValue in
if newValue == .bestValue, !fuelSavingsSettings.isConfigured {
alertsMode = .cheapest
FuelStore.saveAlertsMode(.cheapest)
updateAlertMonitor()
return
}
FuelStore.saveAlertsMode(newValue)
updateAlertMonitor()
}
.onChange(of: alertsBestValueThresholdGBP) { _, newValue in
FuelStore.saveAlertsBestValueThresholdGBP(newValue)
updateAlertMonitor()
@@ -1609,9 +1586,6 @@ struct ContentView: View {
enabled: $alertsEnabled,
radius: $alertsRadius,
fuel: $alertsFuel,
alertMode: $alertsMode,
bestValueThresholdGBP: alertsBestValueThresholdGBP,
bestValueConfigured: fuelSavingsSettings.isConfigured,
favouriteDropEnabled: $favouriteDropAlertsEnabled,
favouriteDropFuel: $favouriteDropAlertsFuel,
followsSearch: $alertsFollowsSearch,
@@ -2053,9 +2027,7 @@ struct StationRow: View {
// scaled down or truncated.
if let price = station.prices[fuel] {
HStack(spacing: 5) {
Circle()
.fill(ragColor)
.frame(width: 8, height: 8)
PriceRatingDot(color: ragColor)
FuelStore.priceTextAttributed(price, style: priceDisplayStyle,
size: priceSize, weight: .bold)
.lineLimit(1)
@@ -2088,8 +2060,7 @@ struct StationRow: View {
Button {
onToggleFavourite()
} label: {
Image(systemName: isFavourite ? "star.fill" : "star")
.foregroundStyle(isFavourite ? .yellow : .secondary)
FavouriteStarGlyph(isFavourite: isFavourite)
}
.buttonStyle(.borderless)
}
@@ -2110,14 +2081,7 @@ struct StationRow: View {
/// app: coloured text keeps its contrast in both appearances, at every
/// Dynamic Type size, without introducing a colour outside the palette.
private func badgeCapsule(_ badge: StationRecommendationBadge) -> some View {
Text(badge.title)
.font(.caption2.bold())
.foregroundStyle(badge.tint)
.padding(.horizontal, 7)
.padding(.vertical, 3)
.background(Capsule().fill(badge.tint.opacity(0.15)))
.lineLimit(1)
.fixedSize()
StationLabelBadge(label: badge.title, color: badge.tint)
}
}