Alerts: mile-friendly radius 1/2/3/5/8 + Follow search (capped 8 mi); Live Activity Follow search; legacy radii clamped

This commit is contained in:
FuelBoard Contributor
2026-08-13 15:00:53 +01:00
parent bb48995fac
commit 490c343fd5
5 changed files with 210 additions and 27 deletions
+44 -5
View File
@@ -261,6 +261,8 @@ struct FuelStore {
static let alertsEnabledKey = "fuelboard.alertsEnabled" // Bool
static let alertsRadiusKey = "fuelboard.alertsRadius" // Double km
static let alertsFuelKey = "fuelboard.alertsFuel" // FuelType raw value
static let alertsFollowSearchKey = "fuelboard.alertsFollowSearch" // Bool alerts mirror the Stations-tab distance
static let liveActivityFollowSearchKey = "fuelboard.liveActivityFollowSearch" // Bool Live Activity mirrors the Stations-tab distance
static let debugModeKey = "fuelboard.debugMode" // Bool hidden dev flag
static let liveActivityKey = "fuelboard.liveActivity" // Bool Live Activity toggle
static let liveActivityFuelKey = "fuelboard.liveActivityFuel" // FuelType raw value
@@ -483,13 +485,29 @@ struct FuelStore {
}
/// Alert trigger-radius options (in the user's display unit, mapped to km
/// on selection). Expanded beyond the Live Activity's 5/10/15 so approach
/// alerts can trigger close (13) or wide (up to 20).
static let alertRadiusOptions = [1, 2, 3, 5, 8, 10, 15, 20]
/// on selection). Mile-friendly approach distances: 12 city, 3 town,
/// 5 default (matches the Stations-tab default), 8 motorway/long approach.
/// The geofence caps here wider circles register poorly on iOS
/// (kCLErrorRegionMonitoringFailure, entry latency, battery) and the
/// "approach" signal dissolves beyond ~8 miles.
static let alertRadiusOptions = [1, 2, 3, 5, 8]
/// "Follow search" caps the geofence at 8 miles so a 10/15-mile Stations
/// search never creates huge region-monitoring circles.
static let alertFollowCapKM: Double = 8 * 1.60934
/// The effective alert radius: the manual radius, or when alerts follow
/// the Stations-tab search that distance (miles, converted via the
/// user's unit) capped at 8 miles.
static func effectiveAlertsRadiusKM(followsSearch: Bool, manualKM: Double) -> Double {
guard followsSearch else { return manualKM }
return min(loadDistanceUnit().toKM(Double(loadStationLimit())), alertFollowCapKM)
}
static func loadAlertsRadius() -> Double {
if let raw = loadString(service: alertsRadiusKey), let value = Double(raw), value >= 1, value <= 33 {
return value
if let raw = loadString(service: alertsRadiusKey), let value = Double(raw), value >= 1 {
// Clamp legacy values (old options went to 20 km) to the new cap.
return min(value, alertFollowCapKM)
}
return 3.0
}
@@ -498,6 +516,16 @@ struct FuelStore {
saveString(String(radius), service: alertsRadiusKey)
}
/// Whether alerts mirror the Stations-tab search distance instead of the
/// manual radius. Defaults to OFF so existing behaviour is unchanged.
static func loadAlertsFollowsSearch() -> Bool {
loadString(service: alertsFollowSearchKey) == "1"
}
static func saveAlertsFollowsSearch(_ enabled: Bool) {
saveString(enabled ? "1" : "0", service: alertsFollowSearchKey)
}
// MARK: Live Activity
static func loadLiveActivityEnabled() -> Bool {
@@ -534,6 +562,17 @@ struct FuelStore {
saveString(String(miles), service: liveActivityRadiusKey)
}
/// Whether the Live Activity mirrors the Stations-tab search distance
/// instead of its own saved radius. Defaults to OFF so existing
/// behaviour is unchanged.
static func loadLiveActivityFollowsSearch() -> Bool {
loadString(service: liveActivityFollowSearchKey) == "1"
}
static func saveLiveActivityFollowsSearch(_ enabled: Bool) {
saveString(enabled ? "1" : "0", service: liveActivityFollowSearchKey)
}
// MARK: Refresh policy data is cached; the app only auto-refreshes
// twice a day (pull-to-refresh is the manual override).