widget + live activity: prefer Apple-Maps road distance, computed by the app

The widget/Live Activity showed straight-line haversine distance (0.8 mi)
while Apple Maps routes 1.8 mi. Road routing is too heavy for the widget
execution + ~40-70/day refresh budget, so the APP now computes it:

- New RoadDistanceService (app target): for the nearest 12 stations each
  pass, calls MapKit MKDirections (free, no API key, matches Apple Maps)
  and caches metres keyed by station ID.
- Cache stored in KEYCHAIN (fuelboard.roadDistances) so the widget reads it
  even on free SideStore accounts with no app-group container; only valid
  within 600 m of the location it was built from.
- Throttled: recompute max every 10 min, or when the user moves > 400 m;
  wired into the location-update hook + location onChange.
- Widget face, app station rows + Live Activity show the cached road
  distance, falling back to straight-line when absent.
- 4 new cache tests (105 total).
This commit is contained in:
FuelBoard Contributor
2026-08-20 11:33:47 +01:00
parent 28bb245c93
commit 64ef260959
6 changed files with 215 additions and 4 deletions
+17 -1
View File
@@ -50,6 +50,21 @@ struct ContentView: View {
// fires on every fix incl. background significant-change
// wake-ups, so the Lock Screen pill stays live while driving.
updateLiveActivity()
refreshRoadDistancesIfNeeded()
}
}
/// Kicks off a (throttled) Apple-Maps road-distance recompute for the
/// stations around the current fix. The app owns routing the widget and
/// Live Activity only read the cached result.
private func refreshRoadDistancesIfNeeded() {
guard let location else { return }
Task {
await RoadDistanceService.refreshIfNeeded(
stations: stations,
lat: location.lat,
lng: location.lng
)
}
}
@@ -377,6 +392,7 @@ struct ContentView: View {
monitor.update(stations: stations, favourites: refreshedFavourites,
fuel: alertsFuel, radiusKM: effectiveAlertsRadiusKM)
updateLiveActivity()
refreshRoadDistancesIfNeeded()
}
}
)
@@ -949,7 +965,7 @@ struct StationRow: View {
.lineLimit(1)
.truncationMode(.tail)
if let location {
Text(distanceUnit.format(station.distanceKM(to: location.lat, lng2: location.lng)))
Text(distanceUnit.format(FuelStore.displayDistanceKM(station: station, userLat: location.lat, userLng: location.lng)))
.font(.caption2)
.foregroundStyle(.secondary)
.monospacedDigit()