From 3303035c041d94cc8440a01cd677b7e6ee983c47 Mon Sep 17 00:00:00 2001 From: FuelBoard Contributor Date: Wed, 12 Aug 2026 14:50:09 +0100 Subject: [PATCH] =?UTF-8?q?Alerts:=20trigger=20on=20approach=20=E2=80=94?= =?UTF-8?q?=20geofence=20radius=20=3D=20alert=20radius?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Region radius changed from fixed 300 m forecourt fence to the alert radius (the Alerts-tab slider), clamped to the device's maximumRegionMonitoringDistance. - Notification now fires when you ENTER the winner's circle while approaching, not when you reach the forecourt. - Cheapest-gate unchanged: still only fires when the entered station is the cheapest within the alert radius (fresh prices). Dedup still 1/ station/hour. 35 tests pass. --- FuelBoard/ProximityMonitor.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/FuelBoard/ProximityMonitor.swift b/FuelBoard/ProximityMonitor.swift index 859eed7..1f7e8b1 100644 --- a/FuelBoard/ProximityMonitor.swift +++ b/FuelBoard/ProximityMonitor.swift @@ -41,6 +41,9 @@ struct TieChoice { /// /// Region budget: iOS allows 20 monitored regions per app — favourites are /// always registered first, then the closest remaining stations fill the rest. +/// Each region is a circle around a station with radius = the alert radius +/// (clamped to the device ceiling), so the notification fires on APPROACH — +/// entering the winner's circle — not at the forecourt. @MainActor final class ProximityMonitor: NSObject, ObservableObject, @preconcurrency CLLocationManagerDelegate, UNUserNotificationCenterDelegate { @Published var monitoredStationIDs: [String] = [] @@ -117,10 +120,17 @@ final class ProximityMonitor: NSObject, ObservableObject, @preconcurrency CLLoca candidates.append(contentsOf: others) var registered: [String] = [] + // Trigger radius = the ALERT radius (approach heads-up), not a tiny + // forecourt fence: entering the winner's region means you're within + // the alert radius of it, and the notification fires as you approach. + // Clamp to the device's region-monitoring ceiling — registering a + // larger region fails (kCLErrorRegionMonitoringFailure) or is reduced. + let maxRegion = manager.maximumRegionMonitoringDistance + let triggerRadius = maxRegion > 0 ? min(radiusKM * 1000, maxRegion) : radiusKM * 1000 for station in candidates.prefix(18) where station.prices[fuel] != nil { let region = CLCircularRegion( center: CLLocationCoordinate2D(latitude: station.lat, longitude: station.lng), - radius: 300, + radius: triggerRadius, identifier: station.id ) region.notifyOnEntry = true