Update FuelBoard onboarding copy, title formatting, key sheet card wrapping, and best-value button placement

This commit is contained in:
FuelBoard Contributor
2026-10-14 16:22:31 +01:00
parent f04a0fe0f5
commit 558f956d8d
5 changed files with 50 additions and 31 deletions
+17
View File
@@ -78,6 +78,7 @@ struct FuelSavingsSummaryView: View {
/// Sizes the "extra return travel" caption in the user's own unit (the /// Sizes the "extra return travel" caption in the user's own unit (the
/// stored figure is miles) — used by both the row banner and the detail sheet. /// stored figure is miles) — used by both the row banner and the detail sheet.
var distanceUnit: DistanceUnit = .miles var distanceUnit: DistanceUnit = .miles
var showInfoAction: (() -> Void)? = nil
var body: some View { var body: some View {
switch availability { switch availability {
@@ -166,6 +167,22 @@ struct FuelSavingsSummaryView: View {
.font(.caption2) .font(.caption2)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
if let showInfoAction {
Rectangle()
.fill(Color(.separator))
.frame(height: 0.5)
.padding(.vertical, 4)
Button(action: showInfoAction) {
HStack(spacing: 8) {
Text("How Best Value works")
Spacer(minLength: 8)
Image(systemName: "chevron.forward")
.font(.footnote.weight(.semibold))
.foregroundStyle(.tertiary)
}
}
.buttonStyle(.plain)
}
} }
} }
case .unavailable(let reason): case .unavailable(let reason):
+19 -13
View File
@@ -108,10 +108,10 @@ struct OnboardingView: View {
.frame(width: 96, height: 96) .frame(width: 96, height: 96)
.clipShape(RoundedRectangle(cornerRadius: 21, style: .continuous)) .clipShape(RoundedRectangle(cornerRadius: 21, style: .continuous))
} }
.padding(.bottom, 28) .padding(.bottom, 16)
Text(String(localized: "Welcome to FuelBoard", comment: "Onboarding welcome title")) Text(String(localized: "Welcome to\nFuelBoard", comment: "Onboarding welcome title"))
.font(.largeTitle.bold()) .font(.title.bold())
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
Text(String(localized: "The cheapest petrol, diesel and premium fuel near you — from the official UK Fuel Finder data.", comment: "Onboarding welcome subtitle")) Text(String(localized: "The cheapest petrol, diesel and premium fuel near you — from the official UK Fuel Finder data.", comment: "Onboarding welcome subtitle"))
@@ -121,12 +121,13 @@ struct OnboardingView: View {
.padding(.horizontal, 32) .padding(.horizontal, 32)
.padding(.top, 12) .padding(.top, 12)
featureRow(icon: "globe.europe.africa.fill", text: String(localized: "UK-wide prices — 8,000+ stations, updated twice a day", comment: "Onboarding feature row")) featureRow(icon: "map.fill", title: String(localized: "Compare fuel prices near you", comment: "Onboarding feature row title"), subtitle: String(localized: "Over 8,000 stations across Great Britain, with prices updated daily.", comment: "Onboarding feature row subtitle"))
featureRow(icon: "scope", text: String(localized: "Cheapest within your chosen radius, or closest station first", comment: "Onboarding feature row"))
featureRow(icon: "star.fill", text: String(localized: "Favourites with instant price comparison", comment: "Onboarding feature row")) featureRow(icon: "chart.line.uptrend.xyaxis", title: String(localized: "See how much you could save", comment: "Onboarding feature row title"), subtitle: String(localized: "Compare fill-up costs and potential savings at different stations.", comment: "Onboarding feature row subtitle"))
featureRow(icon: "chart.bar.fill", text: String(localized: "Green/amber/red rating vs the best nearby price", comment: "Onboarding feature row"))
featureRow(icon: "square.grid.2x2.fill", text: String(localized: "Home-screen widget showing the cheapest nearby", comment: "Onboarding feature row")) featureRow(icon: "car.fill", title: String(localized: "Know if it's worth the drive", comment: "Onboarding feature row title"), subtitle: String(localized: "Factor in your journey and fuel costs to find the best value.", comment: "Onboarding feature row subtitle"))
featureRow(icon: "bell.fill", text: String(localized: "Alerts when you approach the cheapest station", comment: "Onboarding feature row"))
featureRow(icon: "star.fill", title: String(localized: "Keep your favourites close", comment: "Onboarding feature row title"), subtitle: String(localized: "Save stations and quickly compare their latest prices.", comment: "Onboarding feature row subtitle"))
Spacer() Spacer()
} }
@@ -298,18 +299,23 @@ struct OnboardingView: View {
OnboardingPrimaryButton(title: title, action: action) OnboardingPrimaryButton(title: title, action: action)
} }
private func featureRow(icon: String, text: String) -> some View { private func featureRow(icon: String, title: String, subtitle: String) -> some View {
HStack(spacing: 12) { HStack(alignment: .top, spacing: 12) {
Image(systemName: icon) Image(systemName: icon)
.font(.system(size: 20, weight: .semibold)) .font(.system(size: 20, weight: .semibold))
.foregroundStyle(Color.accentColor) .foregroundStyle(Color.accentColor)
.frame(width: 32) .frame(width: 32)
Text(text) VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(.subheadline.bold())
Text(subtitle)
.font(.subheadline) .font(.subheadline)
.foregroundStyle(.secondary)
}
Spacer(minLength: 0) Spacer(minLength: 0)
} }
.padding(.horizontal, 36) .padding(.horizontal, 36)
.padding(.vertical, 10) .padding(.vertical, 8)
} }
private func permissionStatusLabel(granted: Bool, denied: Bool, deniedText: String) -> some View { private func permissionStatusLabel(granted: Bool, denied: Bool, deniedText: String) -> some View {
+2 -8
View File
@@ -238,18 +238,12 @@ struct StationDetailView: View {
availability: fuelSavings, availability: fuelSavings,
amount: fuelSavingsAmount, amount: fuelSavingsAmount,
baselineStationName: savingsBaselineStationName, baselineStationName: savingsBaselineStationName,
distanceUnit: distanceUnit distanceUnit: distanceUnit,
showInfoAction: { showBestValueInfo = true }
) )
} 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
+8 -6
View File
@@ -267,14 +267,14 @@ struct StationsView: View {
keyCard( keyCard(
icon: "checkmark.circle.fill", color: .green, icon: "checkmark.circle.fill", color: .green,
title: "Good price", blurb: "Within 1.5p of the cheapest", title: "Good price", blurb: "Within 1.5p of the cheapest",
threshold: "≤1.5p") threshold: "0–1.5p")
keyCard( keyCard(
icon: "equal.circle.fill", color: .orange, icon: "equal.circle.fill", color: .orange,
title: "Okay", blurb: "Within 4p of the cheapest", title: "Okay", blurb: "More than 1.5p, up to 4p above the cheapest",
threshold: "≤4p") threshold: ">1.5–4p")
keyCard( keyCard(
icon: "exclamationmark.circle.fill", color: .red, icon: "exclamationmark.circle.fill", color: .red,
title: "Pricey", blurb: "More than 4p over the cheapest", title: "Pricey", blurb: "More than 4p above the cheapest",
threshold: ">4p") threshold: ">4p")
Rectangle() Rectangle()
.fill(Color(.separator)) .fill(Color(.separator))
@@ -306,10 +306,11 @@ struct StationsView: View {
.padding(.horizontal, 14) .padding(.horizontal, 14)
.padding(.vertical, 12) .padding(.vertical, 12)
.keyCardFill(color: .yellow) .keyCardFill(color: .yellow)
Text("Colours match each station's rating on the list") Text("Price differences are shown in pence per litre (p/litre). Colours match each station's rating on the list.")
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity, alignment: .center)
.multilineTextAlignment(.center)
.padding(.top, 4) .padding(.top, 4)
} }
.listRowBackground(Color.clear) .listRowBackground(Color.clear)
@@ -377,6 +378,7 @@ struct StationsView: View {
Text(LocalizedStringKey(blurb)) Text(LocalizedStringKey(blurb))
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
} }
Spacer(minLength: 8) Spacer(minLength: 8)
HStack(spacing: 6) { HStack(spacing: 6) {
+3 -3
View File
@@ -26,15 +26,15 @@
"Good price" = "Good price"; "Good price" = "Good price";
"Within 1.5p of the cheapest" = "Within 1.5p of the cheapest"; "Within 1.5p of the cheapest" = "Within 1.5p of the cheapest";
"Okay" = "Okay"; "Okay" = "Okay";
"Within 4p of the cheapest" = "Within 4p of the cheapest"; "More than 1.5p, up to 4p above the cheapest" = "More than 1.5p, up to 4p above the cheapest";
"Pricey" = "Pricey"; "Pricey" = "Pricey";
"More than 4p over the cheapest" = "More than 4p over the cheapest"; "More than 4p above the cheapest" = "More than 4p above the cheapest";
"Top result for the current sort" = "Top result for the current sort"; "Top result for the current sort" = "Top result for the current sort";
"Star a station to add it to Favourites" = "Star a station to add it to Favourites"; "Star a station to add it to Favourites" = "Star a station to add it to Favourites";
/* Star button in the More info sheet heading (accessibility labels) */ /* Star button in the More info sheet heading (accessibility labels) */
"Add to favourites" = "Add to favourites"; "Add to favourites" = "Add to favourites";
"Remove from favourites" = "Remove from favourites"; "Remove from favourites" = "Remove from favourites";
"Colours match each station's rating on the list" = "Colours match each station's rating on the list"; "Price differences are shown in pence per litre (p/litre). Colours match each station's rating on the list." = "Price differences are shown in pence per litre (p/litre). Colours match each station's rating on the list.";
"How Best Value works" = "How Best Value works"; "How Best Value works" = "How Best Value works";
"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." = "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."; "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." = "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.";
"Your fill-up amount and MPG are used to show whether you could save overall." = "Your fill-up amount and MPG are used to show whether you could save overall."; "Your fill-up amount and MPG are used to show whether you could save overall." = "Your fill-up amount and MPG are used to show whether you could save overall.";