Alerts tab: picker-style config matching Live Activity; move Live Activity there
- Alerts section restyled to mirror the Live Activity section: one section with toggle + Fuel picker + Radius picker (was segmented fuel + slider) - Alert radius options expanded to 1/2/3/5/8/10/15/20 (display unit, snapped to nearest option; stored km unchanged; validation widened to 33 km) - Live Activity section moved from Settings into the Alerts tab so both 'notify while driving' surfaces sit together - SettingsView loses the Live Activity bindings/section
This commit is contained in:
+57
-35
@@ -3,59 +3,63 @@ import SwiftUI
|
|||||||
/// Alerts tab — enables the cheapest-station proximity alerts and shows what's
|
/// Alerts tab — enables the cheapest-station proximity alerts and shows what's
|
||||||
/// being monitored. The fuel being monitored is chosen HERE, independently of
|
/// being monitored. The fuel being monitored is chosen HERE, independently of
|
||||||
/// the Stations-tab selection: users can browse any fuel without re-targeting
|
/// 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 {
|
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)
|
||||||
@Binding var fuel: FuelType // the fuel alerts monitor for
|
@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 distanceUnit: DistanceUnit
|
||||||
let monitoredCount: Int
|
let monitoredCount: Int
|
||||||
let lastAlert: String?
|
let lastAlert: String?
|
||||||
|
|
||||||
/// The radius slider works in the user's chosen unit; the stored value
|
/// The radius picker works in the user's chosen unit; the stored value
|
||||||
/// stays km so ProximityMonitor and persistence never change.
|
/// stays km so ProximityMonitor and persistence never change. Options are
|
||||||
private var radiusInUnit: Double { distanceUnit.fromKM(radius) }
|
/// snapped to the nearest picker value so any stored radius still selects.
|
||||||
|
private var radiusOption: Binding<Int> {
|
||||||
|
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 {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
List {
|
List {
|
||||||
Section {
|
Section {
|
||||||
Toggle("Cheapest-station alerts", isOn: $enabled)
|
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: {
|
} 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 {
|
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") {
|
Section("Monitoring") {
|
||||||
if monitoredCount == 0 {
|
if monitoredCount == 0 {
|
||||||
Text("No stations monitored yet — open the Stations tab to load prices first.")
|
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")
|
.navigationTitle("Alerts")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,9 @@ struct ContentView: View {
|
|||||||
enabled: $alertsEnabled,
|
enabled: $alertsEnabled,
|
||||||
radius: $alertsRadius,
|
radius: $alertsRadius,
|
||||||
fuel: $alertsFuel,
|
fuel: $alertsFuel,
|
||||||
|
liveActivityEnabled: $liveActivityEnabled,
|
||||||
|
liveActivityFuel: $liveActivityFuel,
|
||||||
|
liveActivityRadiusMiles: $liveActivityRadiusMiles,
|
||||||
distanceUnit: distanceUnit,
|
distanceUnit: distanceUnit,
|
||||||
monitoredCount: monitor.monitoredStationIDs.count,
|
monitoredCount: monitor.monitoredStationIDs.count,
|
||||||
lastAlert: monitor.lastAlert
|
lastAlert: monitor.lastAlert
|
||||||
@@ -325,9 +328,6 @@ struct ContentView: View {
|
|||||||
private var settingsTab: some View {
|
private var settingsTab: some View {
|
||||||
SettingsView(
|
SettingsView(
|
||||||
distanceUnit: $distanceUnit,
|
distanceUnit: $distanceUnit,
|
||||||
liveActivityEnabled: $liveActivityEnabled,
|
|
||||||
liveActivityFuel: $liveActivityFuel,
|
|
||||||
liveActivityRadiusMiles: $liveActivityRadiusMiles,
|
|
||||||
alertsFuel: alertsFuel,
|
alertsFuel: alertsFuel,
|
||||||
alertsRadiusKM: alertsRadius,
|
alertsRadiusKM: alertsRadius,
|
||||||
testAlertResult: monitor.lastTestResult,
|
testAlertResult: monitor.lastTestResult,
|
||||||
|
|||||||
@@ -12,14 +12,6 @@ import WidgetKit
|
|||||||
/// with no criteria at all.
|
/// with no criteria at all.
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
@Binding var distanceUnit: DistanceUnit
|
@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
|
/// The fuel + radius currently configured for alerts (mirrors the Alerts
|
||||||
/// tab) so the test notification matches what real alerts will say.
|
/// tab) so the test notification matches what real alerts will say.
|
||||||
var alertsFuel: FuelType = .e10
|
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.")
|
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 {
|
Section {
|
||||||
Button {
|
Button {
|
||||||
onShowOnboarding()
|
onShowOnboarding()
|
||||||
|
|||||||
@@ -482,8 +482,13 @@ struct FuelStore {
|
|||||||
saveString(enabled ? "1" : "0", service: alertsEnabledKey)
|
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 {
|
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 value
|
||||||
}
|
}
|
||||||
return 3.0
|
return 3.0
|
||||||
|
|||||||
Reference in New Issue
Block a user