Live-test fixes: CarPlay disfavor, widget location refresh, debug cheapest parity

1. CarPlay widget taps could never launch Maps — FuelBoard is not a
   CarPlay app, and Apple only lets a widget launch its own app in
   CarPlay when that app is CarPlay-enabled. Marked the widget as a
   disfavored CarPlay location (iOS 26+; no-op below): read-only in
   the car, no dead tap. Header comment updated to match reality.

2. Widgets now track the user's location: timeline refresh 15m -> 5m,
   and the app's location manager reloads all widget timelines after
   every significant move (250m filter, throttled to 60s), so the
   widget re-orders around the new position while driving instead of
   waiting for the next tick.

3. Debug 'Cheapest in range' no longer shows the ALERT prediction
   (alertsFuel + alert radius — a different pool by design). It now
   mirrors the app list exactly: the TOP-badge station for the
   selected fuel within the stationLimit radius (new DebugAppCheapest
   computed in ContentView, passed to Settings). Alert-prediction
   cheapest kept as fallback when the app view hasn't resolved.
This commit is contained in:
FuelBoard Contributor
2026-08-12 18:01:29 +01:00
parent d37aab04f1
commit f822bd2636
4 changed files with 95 additions and 13 deletions
+19 -4
View File
@@ -29,6 +29,9 @@ struct SettingsView: View {
var onShowOnboarding: () -> Void = {}
/// Live snapshot of the device fix + stations in range (from the monitor).
var debugStatus: DebugLocationStatus?
/// The app's own TOP-badge cheapest (selected fuel + stationLimit radius),
/// so the debug "Cheapest in range" row matches the app list exactly.
var appCheapest: DebugAppCheapest?
@StateObject private var tipStore = TipStore()
@State private var showTipAlert = false
@@ -96,7 +99,7 @@ struct SettingsView: View {
} header: {
Text("Debug")
} footer: {
Text("The first button applies the real criteria — cheapest \(alertsFuel.displayName.lowercased()) station within \(distanceUnit.format(alertsRadiusKM)) of you — and sends the actual station's name, price and coordinates. The second fires with no criteria at all (nearest seller), still carrying a real station, just to verify a notification appears and tapping it opens directions.")
Text("The first button applies the real criteria — cheapest \(alertsFuel.displayName.lowercased()) station within \(distanceUnit.format(alertsRadiusKM)) of you — and sends the actual station's name, price and coordinates. The second fires with no criteria at all (nearest seller), still carrying a real station, just to verify a notification appears and tapping it opens directions. Cheapest in range mirrors the app list (selected fuel + distance); the in-range count mirrors the alert radius.")
}
.onAppear { onRefreshDebugStatus() }
}
@@ -180,8 +183,9 @@ struct SettingsView: View {
}
/// Debug location block: device coordinates + fix age, then the in-range
/// indicator. "In range" mirrors the live alert criteria stations selling
/// the monitored fuel within the alert radius of the last fix.
/// indicator. "Cheapest in range" mirrors the app list (selected fuel +
/// stationLimit radius) via `appCheapest`; the "In range" count mirrors
/// the alert prediction (alert fuel + alert radius).
@ViewBuilder
private var debugLocationRows: some View {
Divider()
@@ -226,7 +230,18 @@ struct SettingsView: View {
.foregroundStyle(.secondary)
}
}
if let cheapest = status.cheapestInRange {
if let cheapest = appCheapest {
HStack {
Label("Cheapest in range", systemImage: "fuelpump.fill")
Spacer()
Text(cheapestText(cheapest.station, fuel: cheapest.fuel, distance: cheapest.distanceKM))
.font(.footnote)
.monospacedDigit()
.foregroundStyle(.secondary)
}
} else if let cheapest = status.cheapestInRange {
// Fallback when the app view hasn't resolved yet show the
// alert-prediction cheapest rather than nothing.
HStack {
Label("Cheapest in range", systemImage: "fuelpump.fill")
Spacer()