How Best Value works: its own sheet, opened from the Key sheet and More info sheet
The explanation used to be a section inside the Key sheet. It is a standalone HowBestValueSheet now, opened from a tappable row with a chevron: at the bottom of the Key sheet, and at the bottom of the More info sheet below the Best Value card. Gating: the More info row sits INSIDE the same `if let fuelSavings` as the card it follows, so it disappears with the feature. Verified with Best Value toggled off in Settings: the More info sheet then shows neither the card nor the row. The Key sheet row is deliberately ungated — that sheet is the legend and is reachable whatever the feature is doing. Copy is unchanged: the same Localizable.strings strings are reused, and the old section header became the sheet's title. A DEBUG-only `-showBestValueInfo` hook opens the sheet without a tap.
This commit is contained in:
@@ -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
|
||||||
@@ -33,6 +33,9 @@ struct StationDetailView: View {
|
|||||||
/// the caption fades out on its own.
|
/// the caption fades out on its own.
|
||||||
@State private var showingCopied = false
|
@State private var showingCopied = false
|
||||||
@State private var copyResetTask: Task<Void, Never>?
|
@State private var copyResetTask: Task<Void, Never>?
|
||||||
|
/// 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")!
|
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: {
|
} header: {
|
||||||
Text("Best Value")
|
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
|
#if DEBUG
|
||||||
@@ -265,6 +275,8 @@ struct StationDetailView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text("Are you sure you want to report an error in fuel prices or forecourt details? This will open in your chosen browser.")
|
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 {
|
.toolbar {
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
Button("Done") { dismiss() }
|
Button("Done") { dismiss() }
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ struct StationsView: View {
|
|||||||
/// info button in the header — keeps the list focused on stations.
|
/// info button in the header — keeps the list focused on stations.
|
||||||
@State private var showKey = false
|
@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).
|
/// Station picked for the More-info sheet (tap action = showDetails).
|
||||||
@State private var detailStation: FuelStation?
|
@State private var detailStation: FuelStation?
|
||||||
|
|
||||||
@@ -218,6 +223,18 @@ struct StationsView: View {
|
|||||||
if ProcessInfo.processInfo.arguments.contains("-showKeySheet") {
|
if ProcessInfo.processInfo.arguments.contains("-showKeySheet") {
|
||||||
showKey = true
|
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
|
#endif
|
||||||
}
|
}
|
||||||
.task(id: stations.count) {
|
.task(id: stations.count) {
|
||||||
@@ -299,15 +316,12 @@ struct StationsView: View {
|
|||||||
.listRowInsets(EdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16))
|
.listRowInsets(EdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16))
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("How Best Value works") {
|
// The explanation is its own sheet now, opened from this
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
// row. Deliberately NOT gated by Best Value: the Key sheet
|
||||||
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.")
|
// is the legend and is reachable whatever the feature is
|
||||||
Text("Your fill-up amount and MPG are used to show whether you could save overall.")
|
// doing.
|
||||||
Text("Results are marked “Approx.” until road distances are available.")
|
Section {
|
||||||
.foregroundStyle(.secondary)
|
HowBestValueRow { showBestValueInfo = true }
|
||||||
}
|
|
||||||
.font(.subheadline)
|
|
||||||
.padding(.vertical, 4)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Key")
|
.navigationTitle("Key")
|
||||||
@@ -317,6 +331,8 @@ struct StationsView: View {
|
|||||||
Button("Done") { showKey = false }
|
Button("Done") { showKey = false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Nested sheet: the Key sheet stays behind the explanation.
|
||||||
|
.sheet(isPresented: $showBestValueInfo) { HowBestValueSheet() }
|
||||||
}
|
}
|
||||||
.presentationDetents([.fraction(0.6)])
|
.presentationDetents([.fraction(0.6)])
|
||||||
.presentationBackground(Color(UIColor.systemGroupedBackground))
|
.presentationBackground(Color(UIColor.systemGroupedBackground))
|
||||||
|
|||||||
Reference in New Issue
Block a user