Alerts: move geofence monitoring status to Settings debug; plain-language footer

This commit is contained in:
FuelBoard Contributor
2026-08-14 10:34:45 +01:00
parent edf376d378
commit aa95d214e8
3 changed files with 38 additions and 30 deletions
+18 -29
View File
@@ -1,11 +1,13 @@
import SwiftUI import SwiftUI
/// Alerts tab enables the cheapest-station proximity alerts and shows what's /// Alerts tab enables the cheapest-station proximity alerts. The fuel being
/// being monitored. The fuel being monitored is chosen HERE, independently of /// monitored is chosen HERE, independently of the Stations-tab selection:
/// the Stations-tab selection: users can browse any fuel without re-targeting /// users can browse any fuel without re-targeting their alerts (and vice
/// their alerts (and vice versa). The Live Activity section also lives here /// versa). The Live Activity section also lives here it mirrors this page's
/// it mirrors this page's shape (toggle + fuel + distance) so the two /// shape (toggle + fuel + distance) so the two "notify me while driving"
/// "notify me while driving" surfaces sit together. /// surfaces sit together. Geofence plumbing (count, 18-limit) lives in
/// Settings Debug; the user-facing page keeps a plain-language explainer
/// plus a 0-stations hint.
struct AlertsView: View { struct AlertsView: View {
@Binding var enabled: Bool @Binding var enabled: Bool
@Binding var radius: Double // stored in km (monitor + storage) @Binding var radius: Double // stored in km (monitor + storage)
@@ -113,34 +115,21 @@ struct AlertsView: View {
Text("\(value) \(distanceUnit.label(for: Double(value)))").tag(AlertRadiusChoice.fixed(value)) Text("\(value) \(distanceUnit.label(for: Double(value)))").tag(AlertRadiusChoice.fixed(value))
} }
} }
if enabled && monitoredCount == 0 {
Text("No stations monitored yet — open the Stations tab to load prices first.")
.font(.caption)
.foregroundStyle(.secondary)
}
} header: { } header: {
Text("Cheapest-station alerts") Text("Cheapest-station alerts")
} footer: { } footer: {
Text("When you approach a station that is the cheapest within the radius, FuelBoard sends a notification — even with the app closed. Alerts watch for the cheapest \\(fuel.displayName.lowercased()) station within the radius. Follow search mirrors the Stations-tab distance, capped at 8 miles for reliable geofencing.") Text("When you approach a station that is the cheapest within the radius, FuelBoard sends a notification — even with the app closed. Nearby stations selling \(fuel.displayName.lowercased()) are watched — favourites get priority — and each station alerts at most once per hour. Follow search mirrors the Stations-tab distance, capped at 8 miles for reliable geofencing.")
} }
if enabled { if enabled, let lastAlert {
Section("Monitoring") { Section("Last alert") {
if monitoredCount == 0 { Label(lastAlert, systemImage: "bell.badge.fill")
Text("No stations monitored yet — open the Stations tab to load prices first.") .font(.footnote)
.font(.caption)
.foregroundStyle(.secondary)
} else {
LabeledContent("Geofenced stations", value: "\(monitoredCount)")
Text("Your favourites for \(fuel.displayName.lowercased()) get priority, then the closest stations selling that fuel fill the rest (18 max, iOS region limit).")
.font(.caption)
.foregroundStyle(.secondary)
Text("Alerts are checked against the cheapest station within the trigger radius for the fuel chosen above. Each station alerts at most once per hour.")
.font(.caption)
.foregroundStyle(.secondary)
}
}
if let lastAlert {
Section("Last alert") {
Label(lastAlert, systemImage: "bell.badge.fill")
.font(.footnote)
}
} }
} }
+2 -1
View File
@@ -376,7 +376,8 @@ struct ContentView: View {
onShowOnboarding: { showOnboarding = true }, onShowOnboarding: { showOnboarding = true },
debugStatus: monitor.debugStatus, debugStatus: monitor.debugStatus,
appCheapest: appCheapestStatus, appCheapest: appCheapestStatus,
regionError: monitor.lastRegionError regionError: monitor.lastRegionError,
monitoredCount: monitor.monitoredStationIDs.count
) )
.tabItem { Label("Settings", systemImage: "gearshape.fill") } .tabItem { Label("Settings", systemImage: "gearshape.fill") }
} }
+18
View File
@@ -37,6 +37,9 @@ struct SettingsView: View {
/// Last CoreLocation region-monitoring failure (monitor.lastRegionError). /// Last CoreLocation region-monitoring failure (monitor.lastRegionError).
/// Shown in Debug so a silently-failed geofence registration is visible. /// Shown in Debug so a silently-failed geofence registration is visible.
var regionError: String? var regionError: String?
/// Live geofence count (monitor.monitoredStationIDs.count) Debug-only
/// status; the user-facing Alerts tab no longer exposes fence plumbing.
var monitoredCount: Int = 0
@StateObject private var tipStore = TipStore() @StateObject private var tipStore = TipStore()
@State private var showTipAlert = false @State private var showTipAlert = false
@@ -199,6 +202,21 @@ struct SettingsView: View {
} footer: { } footer: {
Text("Connection shows whether prices come from the official Fuel Finder API or the CSV mirror. Data updated is the latest price change reported by the GOV.UK server itself.") Text("Connection shows whether prices come from the official Fuel Finder API or the CSV mirror. Data updated is the latest price change reported by the GOV.UK server itself.")
} }
Section {
if monitoredCount == 0 {
Text("No stations monitored yet — open the Stations tab to load prices first.")
.font(.caption)
.foregroundStyle(.secondary)
} else {
LabeledContent("Geofenced stations", value: "\(monitoredCount)")
}
Text("Favourites for \(alertsFuel.displayName.lowercased()) get priority, then the closest stations selling that fuel fill the rest (18 max, iOS region limit). Each station alerts at most once per hour.")
.font(.caption)
.foregroundStyle(.secondary)
} header: {
Text("Geofence monitoring")
}
} }
Section { Section {