Settings: move test notifications into hidden Debug section + dev flag
- Test-alert buttons moved out of the Alerts section into a Debug section. - Debug section only renders when the debug flag is on; completely hidden otherwise (no user-facing toggle). - Hidden toggle: tap Settings → About → Version five times to flip the flag (keychain + app-group storage, survives reinstall). - About section added with version/build number. - 35 tests pass.
This commit is contained in:
@@ -3,10 +3,13 @@ import StoreKit
|
||||
import UserNotifications
|
||||
import WidgetKit
|
||||
|
||||
/// Settings tab — distance units, onboarding replay, a tip jar, and
|
||||
/// test-alert buttons. The first test button uses REAL data (the actual
|
||||
/// cheapest station for the monitored fuel within the radius, exactly like a
|
||||
/// live alert) via `onTestAlert`; the second fires with no criteria at all.
|
||||
/// Settings tab — distance units, onboarding replay, a tip jar, and an About
|
||||
/// section. Test-notification buttons live in a Debug section that is hidden
|
||||
/// unless the developer debug flag is on (tap the About → Version row five
|
||||
/// times to toggle it — no user-facing switch). The first test button uses
|
||||
/// REAL data (the actual cheapest station for the monitored fuel within the
|
||||
/// radius, exactly like a live alert) via `onTestAlert`; the second fires
|
||||
/// with no criteria at all.
|
||||
struct SettingsView: View {
|
||||
@Binding var distanceUnit: DistanceUnit
|
||||
/// The fuel + radius currently configured for alerts (mirrors the Alerts
|
||||
@@ -27,6 +30,11 @@ struct SettingsView: View {
|
||||
@State private var showTipAlert = false
|
||||
@State private var tipAlertMessage = ""
|
||||
@State private var testAlertMessage: String?
|
||||
/// Hidden developer flag — the Debug section only appears when on. Toggled
|
||||
/// by tapping the About → Version row five times (NOT a user-facing switch).
|
||||
@State private var debugMode: Bool = FuelStore.loadDebugMode()
|
||||
@State private var versionTapCount = 0
|
||||
@State private var lastVersionTap = Date.distantPast
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
@@ -58,31 +66,33 @@ struct SettingsView: View {
|
||||
Text("Replay the welcome screen, including the location and notification permission prompts.")
|
||||
}
|
||||
|
||||
Section {
|
||||
Button {
|
||||
sendTestAlert()
|
||||
} label: {
|
||||
Label("Test alert notification (real data)", systemImage: "bell.badge.fill")
|
||||
if debugMode {
|
||||
Section {
|
||||
Button {
|
||||
sendTestAlert()
|
||||
} label: {
|
||||
Label("Test alert notification (real data)", systemImage: "bell.badge.fill")
|
||||
}
|
||||
if let testAlertMessage {
|
||||
Text(testAlertMessage)
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
if let testAlertResult {
|
||||
Label(testAlertResult, systemImage: "checkmark.circle.fill")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.green)
|
||||
}
|
||||
Button {
|
||||
onPlainTestAlert()
|
||||
} label: {
|
||||
Label("Send plain test notification", systemImage: "bell.slash.fill")
|
||||
}
|
||||
} header: {
|
||||
Text("Debug")
|
||||
} footer: {
|
||||
Text("The first button applies the real criteria — cheapest \(alertsFuel.displayName.lowercased()) station within \(distanceUnit.format(alertsRadiusKM)) of you — and sends the actual station's name, price and coordinates. The second fires with no criteria at all (nearest seller), still carrying a real station, just to verify a notification appears and tapping it opens directions.")
|
||||
}
|
||||
if let testAlertMessage {
|
||||
Text(testAlertMessage)
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
if let testAlertResult {
|
||||
Label(testAlertResult, systemImage: "checkmark.circle.fill")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.green)
|
||||
}
|
||||
Button {
|
||||
onPlainTestAlert()
|
||||
} label: {
|
||||
Label("Send plain test notification", systemImage: "bell.slash.fill")
|
||||
}
|
||||
} header: {
|
||||
Text("Alerts")
|
||||
} footer: {
|
||||
Text("The first button applies the real criteria — cheapest \(alertsFuel.displayName.lowercased()) station within \(distanceUnit.format(alertsRadiusKM)) of you — and sends the actual station's name, price and coordinates. The second fires with no criteria at all (nearest seller), still carrying a real station, just to verify a notification appears and tapping it opens directions.")
|
||||
}
|
||||
|
||||
Section {
|
||||
@@ -105,6 +115,22 @@ struct SettingsView: View {
|
||||
Text("A small tip helps keep the data relay and app development going. Thank you!")
|
||||
}
|
||||
|
||||
Section {
|
||||
HStack {
|
||||
Text("Version")
|
||||
Spacer()
|
||||
Text(appVersion)
|
||||
.foregroundStyle(.secondary)
|
||||
.monospacedDigit()
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { handleVersionTap() }
|
||||
}
|
||||
} header: {
|
||||
Text("About")
|
||||
} footer: {
|
||||
Text("FuelBoard \(appVersion)")
|
||||
}
|
||||
|
||||
if let message = tipStore.message {
|
||||
Section {
|
||||
Text(message)
|
||||
@@ -120,6 +146,29 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var appVersion: String {
|
||||
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0"
|
||||
let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "1"
|
||||
return "\(version) (\(build))"
|
||||
}
|
||||
|
||||
/// Hidden debug-mode toggle: five taps on the Version row flips the flag.
|
||||
/// Nothing in the UI advertises this — the Debug section simply appears or
|
||||
/// disappears. Deliberately NOT a visible switch so end users never see it.
|
||||
private func handleVersionTap() {
|
||||
let now = Date()
|
||||
if now.timeIntervalSince(lastVersionTap) > 0.6 {
|
||||
versionTapCount = 0
|
||||
}
|
||||
lastVersionTap = now
|
||||
versionTapCount += 1
|
||||
if versionTapCount >= 5 {
|
||||
versionTapCount = 0
|
||||
debugMode.toggle()
|
||||
FuelStore.saveDebugMode(debugMode)
|
||||
}
|
||||
}
|
||||
|
||||
private func sendTestAlert() {
|
||||
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||
Task { @MainActor in
|
||||
|
||||
Reference in New Issue
Block a user