diff --git a/FuelBoard/HowBestValueSheet.swift b/FuelBoard/HowBestValueSheet.swift new file mode 100644 index 0000000..644f178 --- /dev/null +++ b/FuelBoard/HowBestValueSheet.swift @@ -0,0 +1,69 @@ +import SwiftUI + +/// The "How Best Value works" explanation as its own sheet. +/// +/// It used to sit inline in the Key sheet as a `Section`; it is one shared view +/// now so the Key sheet and the More info sheet can both open it, and the copy is +/// unchanged (the same `Localizable.strings` keys are reused — the old section +/// header string is the sheet's title). +struct HowBestValueSheet: View { + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + List { + Section { + VStack(alignment: .leading, spacing: 10) { + Text("Best Value compares each station with the closest one. It works out what you could save at the pump, then takes off the fuel used for the extra return journey.") + Text("Your fill-up amount and MPG are used to show whether you could save overall.") + Text("Results are marked “Approx.” until road distances are available.") + .foregroundStyle(.secondary) + } + .font(.subheadline) + .padding(.vertical, 4) + } + } + .navigationTitle("How Best Value works") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button("Done") { dismiss() } + } + } + } + .presentationDetents([.fraction(0.42)]) + .presentationBackground(Color(UIColor.systemGroupedBackground)) + } +} + +/// The row that opens `HowBestValueSheet`. A tappable row with a chevron, like +/// the other rows in both sheets. `.buttonStyle(.plain)` keeps it from becoming a +/// row-wide tap target that could swallow a neighbouring control (see the +/// two-buttons-in-one-row rule). +struct HowBestValueRow: View { + let action: () -> Void + + var body: some View { + Button(action: action) { + HStack(spacing: 8) { + Text("How Best Value works") + Spacer(minLength: 8) + Image(systemName: "chevron.forward") + .font(.footnote.weight(.semibold)) + .foregroundStyle(.tertiary) + } + } + .buttonStyle(.plain) + } +} + +#if DEBUG +/// QA hook support: `-showBestValueInfo` opens this sheet without a tap, so it can +/// be screenshotted (simctl has no tap command) — the same pattern as +/// `-showKeySheet` / `-showDetail`. +enum BestValueInfoQA { + static var isRequested: Bool { + ProcessInfo.processInfo.arguments.contains("-showBestValueInfo") + } +} +#endif diff --git a/FuelBoard/StationDetailView.swift b/FuelBoard/StationDetailView.swift index 996b50a..8362697 100644 --- a/FuelBoard/StationDetailView.swift +++ b/FuelBoard/StationDetailView.swift @@ -33,6 +33,9 @@ struct StationDetailView: View { /// the caption fades out on its own. @State private var showingCopied = false @State private var copyResetTask: Task? + /// The "How Best Value works" sheet, opened from the row at the bottom of the + /// Best Value card (the Key sheet opens the same shared view). + @State private var showBestValueInfo = false private let reportIssueURL = URL(string: "https://www.gov.uk/guidance/report-an-error-in-fuel-prices-or-forecourt-details")! @@ -240,6 +243,13 @@ struct StationDetailView: View { } header: { Text("Best Value") } + + // The explanation lives in its own sheet now, opened from this + // row. The row sits INSIDE the `if let` so it carries the same + // gate as the card above: no card, no row. + Section { + HowBestValueRow { showBestValueInfo = true } + } } #if DEBUG @@ -265,6 +275,8 @@ struct StationDetailView: View { } message: { Text("Are you sure you want to report an error in fuel prices or forecourt details? This will open in your chosen browser.") } + // Nested sheet: the More info sheet stays put behind the explanation. + .sheet(isPresented: $showBestValueInfo) { HowBestValueSheet() } .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Done") { dismiss() } diff --git a/FuelBoard/StationsView.swift b/FuelBoard/StationsView.swift index 34465bf..91e38b2 100644 --- a/FuelBoard/StationsView.swift +++ b/FuelBoard/StationsView.swift @@ -35,6 +35,11 @@ struct StationsView: View { /// info button in the header — keeps the list focused on stations. @State private var showKey = false + /// The "How Best Value works" sheet. The explanation used to be a section in + /// the Key sheet; it is its own sheet now, opened from a row at the bottom of + /// the Key sheet and from the More info sheet. + @State private var showBestValueInfo = false + /// Station picked for the More-info sheet (tap action = showDetails). @State private var detailStation: FuelStation? @@ -218,6 +223,18 @@ struct StationsView: View { if ProcessInfo.processInfo.arguments.contains("-showKeySheet") { showKey = true } + // QA hook: `-showBestValueInfo` opens the Key sheet and then the + // explanation on top of it, so the copy can be screenshotted + // without a tap. It has to chain: the explanation's `.sheet` lives + // inside the Key sheet's content, so it cannot present while the + // Key sheet is shut. + if BestValueInfoQA.isRequested { + showKey = true + Task { @MainActor in + try? await Task.sleep(nanoseconds: 1_200_000_000) + showBestValueInfo = true + } + } #endif } .task(id: stations.count) { @@ -299,15 +316,12 @@ struct StationsView: View { .listRowInsets(EdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16)) } - Section("How Best Value works") { - VStack(alignment: .leading, spacing: 10) { - Text("Best Value compares each station with the closest one. It works out what you could save at the pump, then takes off the fuel used for the extra return journey.") - Text("Your fill-up amount and MPG are used to show whether you could save overall.") - Text("Results are marked “Approx.” until road distances are available.") - .foregroundStyle(.secondary) - } - .font(.subheadline) - .padding(.vertical, 4) + // The explanation is its own sheet now, opened from this + // row. Deliberately NOT gated by Best Value: the Key sheet + // is the legend and is reachable whatever the feature is + // doing. + Section { + HowBestValueRow { showBestValueInfo = true } } } .navigationTitle("Key") @@ -317,6 +331,8 @@ struct StationsView: View { Button("Done") { showKey = false } } } + // Nested sheet: the Key sheet stays behind the explanation. + .sheet(isPresented: $showBestValueInfo) { HowBestValueSheet() } } .presentationDetents([.fraction(0.6)]) .presentationBackground(Color(UIColor.systemGroupedBackground))