Files
fuelboard/FuelBoard/AlertsView.swift
T
FuelBoard Contributor 46d5d45c93 Alerts choose their own fuel, independent of the Stations tab
The proximity monitor was tied to the Stations-tab fuel selection, so
switching the list to another fuel silently re-targeted geofences. Now the
Alerts tab has its own 'Fuel to monitor' picker (persisted as
fuelboard.alertsFuel):
- Monitor reads loadAlertsFuel() on init/background relaunch
- Changing the alerts fuel re-registers geofences around stations selling
  that fuel only
- Favourites priority slots stay scoped to the monitored fuel
- Tests: alerts fuel round-trip + default (33/33 passing)
2026-08-12 08:51:31 +01:00

87 lines
4.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
/// Alerts tab — enables the cheapest-station proximity alerts and shows what's
/// being monitored. The fuel being monitored is chosen HERE, independently of
/// the Stations-tab selection: users can browse any fuel without re-targeting
/// their alerts (and vice versa).
struct AlertsView: View {
@Binding var enabled: Bool
@Binding var radius: Double // stored in km (monitor + storage)
@Binding var fuel: FuelType // the fuel alerts monitor for
let distanceUnit: DistanceUnit
let monitoredCount: Int
let lastAlert: String?
/// The radius slider works in the user's chosen unit; the stored value
/// stays km so ProximityMonitor and persistence never change.
private var radiusInUnit: Double { distanceUnit.fromKM(radius) }
var body: some View {
NavigationStack {
List {
Section {
Toggle("Cheapest-station alerts", isOn: $enabled)
} footer: {
Text("When you approach a station that is the cheapest within the radius, FuelBoard sends a notification — even with the app closed.")
}
if enabled {
Section {
Picker("Fuel", selection: $fuel) {
ForEach(FuelType.allCases) { fuel in
Text(fuel.shortName).tag(fuel)
}
}
.pickerStyle(.segmented)
} header: {
Text("Fuel to monitor")
} footer: {
Text("Alerts watch for the cheapest \(fuel.displayName.lowercased()) station within the radius. Browsing a different fuel in the Stations tab does not change this.")
}
Section("Trigger radius") {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Radius")
Spacer()
Text(String(format: "%.1f %@", radiusInUnit, distanceUnit.shortName))
.foregroundStyle(.secondary)
.monospacedDigit()
}
// 110 in the user's unit; convert back to km on change.
Slider(value: Binding(
get: { radiusInUnit },
set: { radius = distanceUnit.toKM($0) }
), in: 1...10, step: distanceUnit == .kilometers ? 1 : 0.5)
}
}
Section("Monitoring") {
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("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)
}
}
}
}
.navigationTitle("Alerts")
}
}
}