Settings Debug: device coordinates + in-range station indicator

- Debug section now shows the last device fix (lat/lng to 5dp) and its age.
- In-range indicator uses the SAME criteria as live alerts: stations selling
  the monitored fuel within the alert radius of the fix. Green dot + count
  when in range, red when none, grey while waiting for a fix.
- Shows the cheapest in-range station (name, distance, price).
- ProximityMonitor recomputes the snapshot on every location/stations
  change and on Debug-section appear; ContentView passes the live status.
- SettingsView extracted to its own property to keep TabView body within
  the compiler type-check budget. 35 tests pass.
This commit is contained in:
FuelBoard Contributor
2026-08-12 14:33:52 +01:00
parent 07521b1c98
commit 126fdbde62
4 changed files with 173 additions and 10 deletions
+46
View File
@@ -4,6 +4,20 @@ import SwiftUI
import MapKit
import UIKit
/// Debug-only location snapshot shown in Settings Debug. Lets the
/// developer verify location services while testing: the last fix (with age)
/// plus how many stations selling the monitored fuel are within the alert
/// radius of that fix the exact criteria live alerts use.
struct DebugLocationStatus: Equatable {
var coordinate: Coordinate?
var fixAge: TimeInterval?
var fuel: FuelType
var radiusKM: Double
var inRangeCount: Int
var cheapestInRange: FuelStation?
var cheapestDistanceKM: Double?
}
/// A station the user wants to see on a map set when a notification tap
/// can't hand off to Apple Maps (e.g. inside LiveContainer), so the app shows
/// an in-app map with directions instead.
@@ -33,6 +47,9 @@ final class ProximityMonitor: NSObject, ObservableObject, @preconcurrency CLLoca
@Published var lastAlert: String?
@Published private(set) var lastTestResult: String?
@Published var pendingStationMap: StationMapRequest?
/// Debug-only snapshot for the Settings Debug section. Recomputed on
/// every location/stations change via `refreshDebugStatus()`.
@Published private(set) var debugStatus: DebugLocationStatus?
private let manager = CLLocationManager()
private var stations: [FuelStation] = []
@@ -76,6 +93,8 @@ final class ProximityMonitor: NSObject, ObservableObject, @preconcurrency CLLoca
self.fuel = fuel
self.radiusKM = radiusKM
refreshDebugStatus()
for region in manager.monitoredRegions {
manager.stopMonitoring(for: region)
}
@@ -128,6 +147,33 @@ final class ProximityMonitor: NSObject, ObservableObject, @preconcurrency CLLoca
var isEnabled: Bool { enabled }
/// Recomputes the Settings Debug location snapshot from current state.
/// "In range" uses the SAME criteria as live alerts: stations selling the
/// monitored fuel within the alert radius of the last known location.
func refreshDebugStatus() {
guard let fix = FuelStore.loadLocationWithDate() else {
debugStatus = DebugLocationStatus(
coordinate: nil, fixAge: nil,
fuel: fuel, radiusKM: radiusKM,
inRangeCount: 0, cheapestInRange: nil, cheapestDistanceKM: nil)
return
}
let sellers = stations.filter { $0.prices[fuel] != nil }
let inRange = sellers.filter {
$0.distanceKM(to: fix.coordinate.lat, lng2: fix.coordinate.lng) <= radiusKM
}
let cheapest = inRange.min { ($0.prices[fuel] ?? .infinity) < ($1.prices[fuel] ?? .infinity) }
let cheapestDistance = cheapest.map {
$0.distanceKM(to: fix.coordinate.lat, lng2: fix.coordinate.lng)
}
debugStatus = DebugLocationStatus(
coordinate: fix.coordinate,
fixAge: Date().timeIntervalSince(fix.date),
fuel: fuel, radiusKM: radiusKM,
inRangeCount: inRange.count,
cheapestInRange: cheapest, cheapestDistanceKM: cheapestDistance)
}
private func requestPermissions() {
// Region monitoring needs Always location for background delivery.
switch manager.authorizationStatus {