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:
+50
-11
@@ -14,13 +14,21 @@ struct ContentView: View {
|
||||
@State private var alertsEnabled: Bool = FuelStore.loadAlertsEnabled()
|
||||
@State private var alertsRadius: Double = FuelStore.loadAlertsRadius()
|
||||
@State private var alertsFuel: FuelType = FuelStore.loadAlertsFuel()
|
||||
@State private var alertsFollowsSearch: Bool = FuelStore.loadAlertsFollowsSearch()
|
||||
@State private var liveActivityEnabled: Bool = FuelStore.loadLiveActivityEnabled()
|
||||
@State private var liveActivityFuel: FuelType = FuelStore.loadLiveActivityFuel()
|
||||
@State private var liveActivityRadiusMiles: Int = FuelStore.loadLiveActivityRadiusMiles()
|
||||
@State private var liveActivityFollowsSearch: Bool = FuelStore.loadLiveActivityFollowsSearch()
|
||||
@State private var location: Coordinate? = {
|
||||
if let loc = FuelStore.loadLocation() { return Coordinate(lat: loc.lat, lng: loc.lng) }
|
||||
return nil
|
||||
}()
|
||||
|
||||
/// The radius the geofence actually uses: the manual alert radius, or —
|
||||
/// when "Follow search" is on — the Stations-tab distance capped at 8 mi.
|
||||
private var effectiveAlertsRadiusKM: Double {
|
||||
FuelStore.effectiveAlertsRadiusKM(followsSearch: alertsFollowsSearch, manualKM: alertsRadius)
|
||||
}
|
||||
@State private var isLoading = false
|
||||
@State private var statusMessage = ""
|
||||
@State private var showOnboarding = false
|
||||
@@ -159,9 +167,12 @@ struct ContentView: View {
|
||||
enabled: $alertsEnabled,
|
||||
radius: $alertsRadius,
|
||||
fuel: $alertsFuel,
|
||||
followsSearch: $alertsFollowsSearch,
|
||||
liveActivityEnabled: $liveActivityEnabled,
|
||||
liveActivityFuel: $liveActivityFuel,
|
||||
liveActivityRadiusMiles: $liveActivityRadiusMiles,
|
||||
liveActivityFollowsSearch: $liveActivityFollowsSearch,
|
||||
stationLimit: stationLimit,
|
||||
distanceUnit: distanceUnit,
|
||||
monitoredCount: monitor.monitoredStationIDs.count,
|
||||
lastAlert: monitor.lastAlert
|
||||
@@ -193,14 +204,14 @@ struct ContentView: View {
|
||||
// frozen around the last foreground fix.
|
||||
locationManager.onLocationUpdate = { [weak monitor] in
|
||||
monitor?.update(stations: stations, favourites: refreshedFavourites,
|
||||
fuel: alertsFuel, radiusKM: alertsRadius)
|
||||
fuel: alertsFuel, radiusKM: effectiveAlertsRadiusKM)
|
||||
// The Live Activity follows the same wake-ups — this hook
|
||||
// fires on every fix incl. background significant-change
|
||||
// wake-ups, so the Lock Screen pill stays live while driving.
|
||||
updateLiveActivity()
|
||||
}
|
||||
monitor.update(stations: stations, favourites: refreshedFavourites,
|
||||
fuel: alertsFuel, radiusKM: alertsRadius)
|
||||
fuel: alertsFuel, radiusKM: effectiveAlertsRadiusKM)
|
||||
monitor.setEnabled(alertsEnabled)
|
||||
updateLiveActivity()
|
||||
// Refresh only when the cache is stale (twice-a-day policy).
|
||||
@@ -220,7 +231,7 @@ struct ContentView: View {
|
||||
if !showing, FuelStore.loadHasCompletedOnboarding() {
|
||||
locationManager.startForegroundTracking()
|
||||
monitor.update(stations: stations, favourites: refreshedFavourites,
|
||||
fuel: alertsFuel, radiusKM: alertsRadius)
|
||||
fuel: alertsFuel, radiusKM: effectiveAlertsRadiusKM)
|
||||
updateLiveActivity()
|
||||
Task { await refresh(force: true) }
|
||||
}
|
||||
@@ -234,7 +245,7 @@ struct ContentView: View {
|
||||
locationManager.startForegroundTracking()
|
||||
}
|
||||
monitor.update(stations: stations, favourites: refreshedFavourites,
|
||||
fuel: alertsFuel, radiusKM: alertsRadius)
|
||||
fuel: alertsFuel, radiusKM: effectiveAlertsRadiusKM)
|
||||
updateLiveActivity()
|
||||
// No network fetch on foreground — pull-to-refresh is the override.
|
||||
} else {
|
||||
@@ -249,7 +260,7 @@ struct ContentView: View {
|
||||
// Geofences follow the user's position, but the station list is
|
||||
// NOT re-fetched on every movement (cached, twice-a-day policy).
|
||||
monitor.update(stations: stations, favourites: refreshedFavourites,
|
||||
fuel: alertsFuel, radiusKM: alertsRadius)
|
||||
fuel: alertsFuel, radiusKM: effectiveAlertsRadiusKM)
|
||||
updateLiveActivity()
|
||||
}
|
||||
}
|
||||
@@ -264,12 +275,18 @@ struct ContentView: View {
|
||||
// next render.
|
||||
FuelStore.saveStationLimit(newValue)
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
// Follow-search surfaces mirror this distance — re-target.
|
||||
if alertsFollowsSearch {
|
||||
monitor.update(stations: stations, favourites: refreshedFavourites,
|
||||
fuel: alertsFuel, radiusKM: effectiveAlertsRadiusKM)
|
||||
}
|
||||
updateLiveActivity()
|
||||
}
|
||||
.onChange(of: alertsEnabled) { _, newValue in
|
||||
FuelStore.saveAlertsEnabled(newValue)
|
||||
monitor.setEnabled(newValue)
|
||||
monitor.update(stations: stations, favourites: refreshedFavourites,
|
||||
fuel: alertsFuel, radiusKM: alertsRadius)
|
||||
fuel: alertsFuel, radiusKM: effectiveAlertsRadiusKM)
|
||||
if newValue {
|
||||
locationManager.startBackgroundTracking()
|
||||
}
|
||||
@@ -277,14 +294,21 @@ struct ContentView: View {
|
||||
.onChange(of: alertsRadius) { _, newValue in
|
||||
FuelStore.saveAlertsRadius(newValue)
|
||||
monitor.update(stations: stations, favourites: refreshedFavourites,
|
||||
fuel: alertsFuel, radiusKM: alertsRadius)
|
||||
fuel: alertsFuel, radiusKM: effectiveAlertsRadiusKM)
|
||||
}
|
||||
.onChange(of: alertsFuel) { _, newValue in
|
||||
// Alerts fuel is independent of the Stations-tab selection —
|
||||
// changing it re-targets geofences to stations selling that fuel.
|
||||
FuelStore.saveAlertsFuel(newValue)
|
||||
monitor.update(stations: stations, favourites: refreshedFavourites,
|
||||
fuel: newValue, radiusKM: alertsRadius)
|
||||
fuel: newValue, radiusKM: effectiveAlertsRadiusKM)
|
||||
}
|
||||
.onChange(of: alertsFollowsSearch) { _, newValue in
|
||||
// Follow search = the geofence mirrors the Stations-tab distance
|
||||
// (capped at 8 mi); picking any fixed radius clears it.
|
||||
FuelStore.saveAlertsFollowsSearch(newValue)
|
||||
monitor.update(stations: stations, favourites: refreshedFavourites,
|
||||
fuel: alertsFuel, radiusKM: effectiveAlertsRadiusKM)
|
||||
}
|
||||
.onChange(of: liveActivityEnabled) { _, newValue in
|
||||
// Toggling the Live Activity on starts it with the current best
|
||||
@@ -302,10 +326,20 @@ struct ContentView: View {
|
||||
FuelStore.saveLiveActivityRadiusMiles(newValue)
|
||||
updateLiveActivity()
|
||||
}
|
||||
.onChange(of: liveActivityFollowsSearch) { _, newValue in
|
||||
// The pill mirrors the Stations-tab distance instead of its own.
|
||||
FuelStore.saveLiveActivityFollowsSearch(newValue)
|
||||
updateLiveActivity()
|
||||
}
|
||||
.onChange(of: distanceUnit) { _, _ in
|
||||
// Distance unit changes the radius — mirror the new radius in the
|
||||
// Live Activity immediately.
|
||||
updateLiveActivity()
|
||||
// Follow-search alerts also move with the unit.
|
||||
if alertsFollowsSearch {
|
||||
monitor.update(stations: stations, favourites: refreshedFavourites,
|
||||
fuel: alertsFuel, radiusKM: effectiveAlertsRadiusKM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,10 +348,15 @@ struct ContentView: View {
|
||||
/// location/data yet. Uses the Live Activity's OWN fuel + radius (set in
|
||||
/// Settings) — independent of the Stations-tab fuel/distance.
|
||||
private func updateLiveActivity() {
|
||||
// "Follow search" mirrors the Stations-tab distance exactly (no cap —
|
||||
// the pill only displays, it doesn't geofence).
|
||||
let radiusKM = liveActivityFollowsSearch
|
||||
? distanceUnit.toKM(Double(stationLimit))
|
||||
: distanceUnit.toKM(Double(liveActivityRadiusMiles))
|
||||
LiveActivityManager.update(
|
||||
stations: stations,
|
||||
fuel: liveActivityFuel,
|
||||
radiusKM: distanceUnit.toKM(Double(liveActivityRadiusMiles)),
|
||||
radiusKM: radiusKM,
|
||||
location: location,
|
||||
enabled: liveActivityEnabled
|
||||
)
|
||||
@@ -352,7 +391,7 @@ struct ContentView: View {
|
||||
FuelStore.saveFavourites(favourites)
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
monitor.update(stations: stations, favourites: refreshedFavourites,
|
||||
fuel: alertsFuel, radiusKM: alertsRadius)
|
||||
fuel: alertsFuel, radiusKM: effectiveAlertsRadiusKM)
|
||||
}
|
||||
|
||||
/// Fetches fresh prices, but only when the cache is stale — unless
|
||||
@@ -390,7 +429,7 @@ struct ContentView: View {
|
||||
}
|
||||
// Keep monitor geofences in sync with the freshest data.
|
||||
monitor.update(stations: stations, favourites: refreshedFavourites,
|
||||
fuel: alertsFuel, radiusKM: alertsRadius)
|
||||
fuel: alertsFuel, radiusKM: effectiveAlertsRadiusKM)
|
||||
// Fresh prices → refresh the Live Activity pill too.
|
||||
updateLiveActivity()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user