Settings: test alert notification button

Fires a local notification shaped exactly like a real price alert (uses the
currently configured alerts fuel + radius), with permission-gated handling:
prompts on first use, guides to Settings when denied. Tapping it opens
FuelBoard, same as a real alert.
This commit is contained in:
FuelBoard Contributor
2026-08-12 09:15:04 +01:00
parent 3dcfe30dcc
commit d6550409df
3 changed files with 68 additions and 1 deletions
+2
View File
@@ -146,6 +146,8 @@ struct ContentView: View {
SettingsView( SettingsView(
distanceUnit: $distanceUnit, distanceUnit: $distanceUnit,
alertsFuel: alertsFuel,
alertsRadiusKM: alertsRadius,
onShowOnboarding: { showOnboarding = true } onShowOnboarding: { showOnboarding = true }
) )
.tabItem { Label("Settings", systemImage: "gearshape.fill") } .tabItem { Label("Settings", systemImage: "gearshape.fill") }
+15
View File
@@ -172,4 +172,19 @@ final class ProximityMonitor: NSObject, ObservableObject, @preconcurrency CLLoca
lastAlert = "\(brand) · \(String(format: "%.1fp", price)) · \(unit.format(radiusKM)) radius" lastAlert = "\(brand) · \(String(format: "%.1fp", price)) · \(unit.format(radiusKM)) radius"
} }
/// Fires a sample local notification shaped exactly like a real price
/// alert, for the Settings "test" button. Uses a placeholder station so
/// the user can see the alert format without a real entry event. Tapping
/// it does what tapping any FuelBoard alert does opens the app.
static func sendTestNotification(fuel: FuelType, radiusKM: Double) {
let unit = FuelStore.loadDistanceUnit()
let content = UNMutableNotificationContent()
content.title = "Cheapest \(fuel.displayName) nearby: Sample Filling Station"
content.body = "Sample Filling Station is the cheapest within \(unit.format(radiusKM)) at 132.9p. Tap to open."
content.sound = .default
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
}
} }
+51 -1
View File
@@ -1,15 +1,22 @@
import SwiftUI import SwiftUI
import StoreKit import StoreKit
import UserNotifications
import WidgetKit import WidgetKit
/// Settings tab distance units, onboarding replay, and a tip jar. /// Settings tab distance units, onboarding replay, a tip jar, and a
/// test-alert button that previews what a real price notification looks like.
struct SettingsView: View { struct SettingsView: View {
@Binding var distanceUnit: DistanceUnit @Binding var distanceUnit: DistanceUnit
/// 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
var alertsRadiusKM: Double = 3.0
var onShowOnboarding: () -> Void = {} var onShowOnboarding: () -> Void = {}
@StateObject private var tipStore = TipStore() @StateObject private var tipStore = TipStore()
@State private var showTipAlert = false @State private var showTipAlert = false
@State private var tipAlertMessage = "" @State private var tipAlertMessage = ""
@State private var testAlertMessage: String?
var body: some View { var body: some View {
NavigationStack { NavigationStack {
@@ -41,6 +48,23 @@ struct SettingsView: View {
Text("Replay the welcome screen, including the location and notification permission prompts.") Text("Replay the welcome screen, including the location and notification permission prompts.")
} }
Section {
Button {
sendTestAlert()
} label: {
Label("Test alert notification", systemImage: "bell.badge.fill")
}
if let testAlertMessage {
Text(testAlertMessage)
.font(.footnote)
.foregroundStyle(.secondary)
}
} header: {
Text("Alerts")
} footer: {
Text("Sends a notification exactly like a real price alert for \(alertsFuel.displayName.lowercased()) within \(distanceUnit.format(alertsRadiusKM)). Tapping it opens FuelBoard — just like a real alert.")
}
Section { Section {
Button { Button {
Task { await tipStore.purchase() } Task { await tipStore.purchase() }
@@ -75,6 +99,32 @@ struct SettingsView: View {
} }
} }
} }
private func sendTestAlert() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
Task { @MainActor in
switch settings.authorizationStatus {
case .authorized, .provisional, .ephemeral:
ProximityMonitor.sendTestNotification(fuel: alertsFuel, radiusKM: alertsRadiusKM)
testAlertMessage = "Test alert sent — check your notification centre."
case .denied:
testAlertMessage = "Notifications are turned off for FuelBoard. Enable them in Settings → Notifications → FuelBoard, then try again."
default:
// First time ask, then fire if granted.
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
Task { @MainActor in
if granted {
ProximityMonitor.sendTestNotification(fuel: alertsFuel, radiusKM: alertsRadiusKM)
testAlertMessage = "Test alert sent — check your notification centre."
} else {
testAlertMessage = "Notifications weren't allowed, so no test alert was sent."
}
}
}
}
}
}
}
} }
/// Loads the £4.99 tip product and drives its purchase. /// Loads the £4.99 tip product and drives its purchase.