Stop Approx. distances beside routed values; add Best Value alert mode
Distances (the "Approx. 1.5 mi" report): - Refuse junk CoreLocation fixes (invalid accuracy, or a coarse >300 m fix within 3 km of the position already held; a coarse fix in another city is real travel and is still accepted). - Resolve ONE display basis per render through FuelStore.distanceDisplayOrigin, used by rows, radius filter, sorting, savings figures and the Live Activity, so a stray fix can no longer re-base the list outside the route cache's 600 m ring while the routed values sit unused in the cache. - Treat an incomplete route set as a state: retry it after 30 s rather than the full 10-minute throttle, and repair a stale incomplete flag once every in-radius station has a route (nothing left to route, flag stuck down). - Widget extension resolves the same origin for fetch, radius, ordering and labels, and keeps its own fix in fuelboard.widgetLocation instead of overwriting the app's GPS fix. Alerts: - Best Value as a distinct alert mode on the existing geofence/notification pipeline, with a persisted threshold; reverts to cheapest when the feature is unconfigured and is not selectable while disabled. Diagnostics (DEBUG, gated): FB_DIST_FOCUS now prints FB-DIST-ORIGIN (candidate, accuracy, anchor, offset, resolved branch, served origin), FB_FAKE_FIX feeds a synthetic fix through the real guard, FB_STALE_INCOMPLETE forces the stale-flag state, and the route-fill status is surfaced in Settings behind debug mode.
This commit is contained in:
@@ -12,6 +12,9 @@ struct AlertsView: View {
|
||||
@Binding var enabled: Bool
|
||||
@Binding var radius: Double // stored in km (monitor + storage)
|
||||
@Binding var fuel: FuelType // the fuel alerts monitor for
|
||||
@Binding var alertMode: AlertMode
|
||||
@Binding var bestValueThresholdGBP: Double
|
||||
let bestValueConfigured: Bool
|
||||
@Binding var favouriteDropEnabled: Bool
|
||||
@Binding var favouriteDropFuel: FuelType
|
||||
/// Whether alerts mirror the Stations-tab search distance (capped at 8 mi).
|
||||
@@ -150,6 +153,16 @@ struct AlertsView: View {
|
||||
|
||||
Section {
|
||||
Toggle(String(localized: "Cheapest station", comment: "Proximity alert toggle"), isOn: $enabled)
|
||||
Picker(String(localized: "Alert type", comment: "Proximity alert type picker title"), selection: $alertMode) {
|
||||
ForEach(AlertMode.allCases.filter { mode in mode != .bestValue || bestValueConfigured }) { mode in
|
||||
Text(mode.displayName).tag(mode)
|
||||
}
|
||||
}
|
||||
if !bestValueConfigured {
|
||||
Text(String(localized: "Set up Best Value first so Best Value alerts can account for your fill size, MPG and travel cost.", comment: "Best Value alert unavailable hint"))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Picker(String(localized: "Fuel", comment: "Fuel picker title"), selection: $fuel) {
|
||||
ForEach(FuelType.allCases) { fuel in
|
||||
Text(fuel.displayName).tag(fuel)
|
||||
@@ -164,6 +177,18 @@ struct AlertsView: View {
|
||||
Text(alertRadiusGuidance)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
if alertMode == .bestValue {
|
||||
Picker(String(localized: "Minimum saving", comment: "Best Value alert threshold picker title"), selection: $bestValueThresholdGBP) {
|
||||
ForEach(FuelStore.bestValueAlertThresholdOptionsGBP, id: \.self) { value in
|
||||
Text(FuelSavingsFormat.money(value)).tag(value)
|
||||
}
|
||||
}
|
||||
if !bestValueConfigured {
|
||||
Text(String(localized: "Set up Best Value first so FuelBoard can account for your fill size, MPG and travel cost.", comment: "Best Value alert setup hint"))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
if enabled && monitoredCount == 0 {
|
||||
Text(String(localized: "No stations monitored yet — open the Stations tab to load prices first.", comment: "No monitored stations hint"))
|
||||
.font(.caption)
|
||||
@@ -172,7 +197,10 @@ struct AlertsView: View {
|
||||
} header: {
|
||||
Text(String(localized: "Proximity alerts", comment: "Proximity alerts section title"))
|
||||
} footer: {
|
||||
Text(String(format: String(localized: "When you approach a station that is the cheapest within the radius, FuelBoard sends a notification — even with the app closed. Nearby stations selling %@ are watched — favourites get priority — and each station alerts at most once per hour. Follow search mirrors the Stations-tab distance, capped at 8 miles for reliable geofencing. Smaller radii ping more often but later; larger radii ping rarely in town but earlier from afar.", comment: "Proximity alerts section footer"), fuel.displayName.lowercased()))
|
||||
let modeDescription = alertMode == .bestValue
|
||||
? String(format: String(localized: "When you approach the Best Value station within the radius, FuelBoard sends a notification if it could leave you at least %@ better off after extra return travel. Nearby stations selling %@ are watched — favourites get priority — and each station alerts at most once per hour.", comment: "Best Value proximity alerts section footer"), FuelSavingsFormat.money(bestValueThresholdGBP), fuel.displayName.lowercased())
|
||||
: String(format: String(localized: "When you approach a station that is the cheapest within the radius, FuelBoard sends a notification — even with the app closed. Nearby stations selling %@ are watched — favourites get priority — and each station alerts at most once per hour. Follow search mirrors the Stations-tab distance, capped at 8 miles for reliable geofencing. Smaller radii ping more often but later; larger radii ping rarely in town but earlier from afar.", comment: "Proximity alerts section footer"), fuel.displayName.lowercased())
|
||||
Text(modeDescription)
|
||||
}
|
||||
|
||||
if enabled, let lastAlert {
|
||||
|
||||
Reference in New Issue
Block a user