diff --git a/FuelBoard/AlertsView.swift b/FuelBoard/AlertsView.swift index 1d5f6e1..a492745 100644 --- a/FuelBoard/AlertsView.swift +++ b/FuelBoard/AlertsView.swift @@ -3,59 +3,63 @@ 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). +/// their alerts (and vice versa). The Live Activity section also lives here — +/// it mirrors this page's shape (toggle + fuel + distance) so the two +/// "notify me while driving" surfaces sit together. 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 + /// Whether the "cheapest nearby" Live Activity is shown on the Lock Screen + /// / Dynamic Island. Tracks its OWN fuel + radius (below) — independent of + /// the Stations tab. + @Binding var liveActivityEnabled: Bool + /// Fuel the Live Activity tracks (independent of the Stations tab). + @Binding var liveActivityFuel: FuelType + /// Search radius (miles, 5/10/15) the Live Activity uses. + @Binding var liveActivityRadiusMiles: Int 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) } + /// The radius picker works in the user's chosen unit; the stored value + /// stays km so ProximityMonitor and persistence never change. Options are + /// snapped to the nearest picker value so any stored radius still selects. + private var radiusOption: Binding { + Binding( + get: { + let inUnit = distanceUnit.fromKM(radius) + return FuelStore.alertRadiusOptions + .min(by: { abs(Double($0) - inUnit) < abs(Double($1) - inUnit) }) ?? 3 + }, + set: { value in + radius = distanceUnit.toKM(Double(value)) + } + ) + } var body: some View { NavigationStack { List { Section { Toggle("Cheapest-station alerts", isOn: $enabled) + Picker("Fuel", selection: $fuel) { + ForEach(FuelType.allCases) { fuel in + Text(fuel.displayName).tag(fuel) + } + } + Picker("Radius", selection: radiusOption) { + ForEach(FuelStore.alertRadiusOptions, id: \.self) { value in + Text("\(value) \(distanceUnit.label)").tag(value) + } + } + } header: { + Text("Cheapest-station alerts") } footer: { - Text("When you approach a station that is the cheapest within the radius, FuelBoard sends a notification — even with the app closed.") + 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.") } 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.") - } - - Section("Trigger radius") { - VStack(alignment: .leading, spacing: 8) { - HStack { - Text("Radius") - Spacer() - Text(String(format: "%.1f %@", radiusInUnit, distanceUnit.shortName)) - .foregroundStyle(.secondary) - .monospacedDigit() - } - // 1–10 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.") @@ -79,6 +83,24 @@ struct AlertsView: View { } } } + + Section { + Toggle("Live Activity", isOn: $liveActivityEnabled) + Picker("Fuel", selection: $liveActivityFuel) { + ForEach(FuelType.allCases) { fuel in + Text(fuel.displayName).tag(fuel) + } + } + Picker("Distance", selection: $liveActivityRadiusMiles) { + ForEach(FuelStore.stationRadiusOptions, id: \.self) { miles in + Text("\(miles) \(distanceUnit.label)").tag(miles) + } + } + } header: { + Text("Live Activity") + } footer: { + Text("Shows the cheapest station for the fuel and distance you pick here on the Lock Screen and Dynamic Island — independent of the Stations tab. Updates as you drive; tap to open directions. Live Activities can be disabled in System Settings → Live Activities.") + } } .navigationTitle("Alerts") } diff --git a/FuelBoard/ContentView.swift b/FuelBoard/ContentView.swift index 2b4b7ec..6e35fd5 100644 --- a/FuelBoard/ContentView.swift +++ b/FuelBoard/ContentView.swift @@ -159,6 +159,9 @@ struct ContentView: View { enabled: $alertsEnabled, radius: $alertsRadius, fuel: $alertsFuel, + liveActivityEnabled: $liveActivityEnabled, + liveActivityFuel: $liveActivityFuel, + liveActivityRadiusMiles: $liveActivityRadiusMiles, distanceUnit: distanceUnit, monitoredCount: monitor.monitoredStationIDs.count, lastAlert: monitor.lastAlert @@ -325,9 +328,6 @@ struct ContentView: View { private var settingsTab: some View { SettingsView( distanceUnit: $distanceUnit, - liveActivityEnabled: $liveActivityEnabled, - liveActivityFuel: $liveActivityFuel, - liveActivityRadiusMiles: $liveActivityRadiusMiles, alertsFuel: alertsFuel, alertsRadiusKM: alertsRadius, testAlertResult: monitor.lastTestResult, diff --git a/FuelBoard/SettingsView.swift b/FuelBoard/SettingsView.swift index f2fee0b..d6e2eb4 100644 --- a/FuelBoard/SettingsView.swift +++ b/FuelBoard/SettingsView.swift @@ -12,14 +12,6 @@ import WidgetKit /// with no criteria at all. struct SettingsView: View { @Binding var distanceUnit: DistanceUnit - /// Whether the "cheapest nearby" Live Activity is shown on the Lock Screen - /// / Dynamic Island. Tracks its OWN fuel + radius (below) — independent of - /// the Stations tab. - @Binding var liveActivityEnabled: Bool - /// Fuel the Live Activity tracks (independent of the Stations tab). - @Binding var liveActivityFuel: FuelType - /// Search radius (miles, 5/10/15) the Live Activity uses. - @Binding var liveActivityRadiusMiles: Int /// The fuel + radius currently configured for alerts (mirrors the Alerts /// tab) so the test notification matches what real alerts will say. var alertsFuel: FuelType = .e10 @@ -74,33 +66,6 @@ struct SettingsView: View { Text("Distances and search radii across the app, widget and alerts are shown in this unit.") } - Section { - Toggle("Live Activity", isOn: $liveActivityEnabled) - Picker("Fuel", selection: $liveActivityFuel) { - ForEach(FuelType.allCases) { fuel in - Text(fuel.displayName).tag(fuel) - } - } - .onChange(of: liveActivityFuel) { _, newValue in - FuelStore.saveLiveActivityFuel(newValue) - } - Picker("Distance", selection: $liveActivityRadiusMiles) { - ForEach(FuelStore.stationRadiusOptions, id: \.self) { miles in - Text("\(miles) \(distanceUnit.label)").tag(miles) - } - } - .onChange(of: liveActivityRadiusMiles) { _, newValue in - FuelStore.saveLiveActivityRadiusMiles(newValue) - } - } header: { - Text("Live Activity") - } footer: { - Text("Shows the cheapest station for the fuel and distance you pick here on the Lock Screen and Dynamic Island — independent of the Stations tab. Updates as you drive; tap to open directions. Live Activities can be disabled in System Settings → Live Activities.") - } - .onChange(of: liveActivityEnabled) { _, newValue in - FuelStore.saveLiveActivityEnabled(newValue) - } - Section { Button { onShowOnboarding() diff --git a/Shared/FuelStore.swift b/Shared/FuelStore.swift index 4f40ccc..72ba9e4 100644 --- a/Shared/FuelStore.swift +++ b/Shared/FuelStore.swift @@ -482,8 +482,13 @@ struct FuelStore { saveString(enabled ? "1" : "0", service: alertsEnabledKey) } + /// 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 (1–3) or wide (up to 20). + static let alertRadiusOptions = [1, 2, 3, 5, 8, 10, 15, 20] + static func loadAlertsRadius() -> Double { - if let raw = loadString(service: alertsRadiusKey), let value = Double(raw), value >= 1, value <= 10 { + if let raw = loadString(service: alertsRadiusKey), let value = Double(raw), value >= 1, value <= 33 { return value } return 3.0