diff --git a/FuelBoard/OnboardingView.swift b/FuelBoard/OnboardingView.swift index 3b75ffe..6e49778 100644 --- a/FuelBoard/OnboardingView.swift +++ b/FuelBoard/OnboardingView.swift @@ -4,8 +4,9 @@ import UserNotifications import Network /// First-launch onboarding: introduces FuelBoard, then walks the user through -/// the three system permissions (location, notifications, local network for -/// data loading) with in-context prompts. In production it appears once at +/// the two system permissions (location and notifications) with in-context +/// prompts. Price data downloads over HTTPS with no Local Network permission. +/// In production it appears once at /// initial launch (driven by `FuelStore.loadHasCompletedOnboarding()`); a /// test button in the Alerts tab re-opens it anytime. struct OnboardingView: View { @@ -97,23 +98,23 @@ struct OnboardingView: View { } .padding(.bottom, 28) - Text("Welcome to FuelBoard") + Text(String(localized: "Welcome to FuelBoard", comment: "Onboarding welcome title")) .font(.largeTitle.bold()) .multilineTextAlignment(.center) - Text("The cheapest petrol, diesel and premium fuel near you — from the official UK Fuel Finder data.") + Text(String(localized: "The cheapest petrol, diesel and premium fuel near you — from the official UK Fuel Finder data.", comment: "Onboarding welcome subtitle")) .font(.body) .foregroundStyle(.secondary) .multilineTextAlignment(.center) .padding(.horizontal, 32) .padding(.top, 12) - featureRow(icon: "globe.europe.africa.fill", text: "UK-wide prices — 8,000+ stations, updated twice a day") - featureRow(icon: "scope", text: "Cheapest within your chosen radius, or closest station first") - featureRow(icon: "star.fill", text: "Favourites with instant price comparison") - featureRow(icon: "chart.bar.fill", text: "Green/amber/red rating vs the best nearby price") - featureRow(icon: "square.grid.2x2.fill", text: "Home-screen widget showing the cheapest nearby") - featureRow(icon: "bell.fill", text: "Alerts when you approach the cheapest station") + 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: "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.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: "bell.fill", text: String(localized: "Alerts when you approach the cheapest station", comment: "Onboarding feature row")) Spacer() } @@ -129,10 +130,10 @@ struct OnboardingView: View { } .padding(.bottom, 28) - Text("Prices near you") + Text(String(localized: "Prices near you", comment: "Onboarding location permission title")) .font(.largeTitle.bold()) - Text("FuelBoard uses your location to show prices around you and rank stations by distance. Your position never leaves the device.") + Text(String(localized: "FuelBoard uses your location to show prices around you and rank stations by distance. Your position never leaves the device.", comment: "Onboarding location permission explanation")) .font(.body) .foregroundStyle(.secondary) .multilineTextAlignment(.center) @@ -142,7 +143,7 @@ struct OnboardingView: View { permissionStatusLabel( granted: prompter.locationGranted, denied: prompter.locationDenied, - deniedText: "Location was denied. You can still browse all stations, but \"nearest\" sorting needs it." + deniedText: String(localized: "Location was denied. You can still browse all stations, but \"nearest\" sorting needs it.", comment: "Onboarding location denied text") ) .padding(.top, 24) @@ -160,10 +161,10 @@ struct OnboardingView: View { } .padding(.bottom, 28) - Text("Cheapest-station alerts") + Text(String(localized: "Cheapest-station alerts", comment: "Onboarding notifications title")) .font(.largeTitle.bold()) - Text("FuelBoard can notify you when you're approaching the cheapest station in your alert radius — even with the app closed. You can switch this off in the Alerts tab.") + Text(String(localized: "FuelBoard can notify you when you're approaching the cheapest station in your alert radius — even with the app closed. You can switch this off in the Alerts tab.", comment: "Onboarding notifications explanation")) .font(.body) .foregroundStyle(.secondary) .multilineTextAlignment(.center) @@ -173,7 +174,7 @@ struct OnboardingView: View { permissionStatusLabel( granted: prompter.notificationsGranted, denied: prompter.notificationsDenied, - deniedText: "Notifications were denied — you can still use FuelBoard, just without alert banners." + deniedText: String(localized: "Notifications were denied — you can still use FuelBoard, just without alert banners.", comment: "Onboarding notifications denied text") ) .padding(.top, 24) @@ -191,10 +192,10 @@ struct OnboardingView: View { } .padding(.bottom, 28) - Text("Prices, ready when you are") + Text(String(localized: "Prices, ready when you are", comment: "Onboarding data title")) .font(.largeTitle.bold()) - Text("FuelBoard downloads the latest prices from the internet automatically — the full UK dataset, refreshed twice a day. No extra permissions needed.") + Text(String(localized: "FuelBoard downloads the latest prices from the internet automatically — the full UK dataset, refreshed twice a day. No extra permissions needed.", comment: "Onboarding data explanation")) .font(.body) .foregroundStyle(.secondary) .multilineTextAlignment(.center) @@ -218,10 +219,10 @@ struct OnboardingView: View { } .padding(.bottom, 28) - Text("You're all set") + Text(String(localized: "You're all set", comment: "Onboarding final title")) .font(.largeTitle.bold()) - Text("Find your cheapest fuel, add favourites, drop the widget on your Home Screen, and let alerts point you to the best price nearby.") + Text(String(localized: "Find your cheapest fuel, add favourites, drop the widget on your Home Screen, and let alerts point you to the best price nearby.", comment: "Onboarding final explanation")) .font(.body) .foregroundStyle(.secondary) .multilineTextAlignment(.center) @@ -238,10 +239,10 @@ struct OnboardingView: View { private var bottomAction: some View { switch page { case 0: - primaryButton("Continue") { page = 1 } + primaryButton(String(localized: "Continue", comment: "Onboarding continue button")) { page = 1 } case 1: primaryButton( - prompter.locationDenied ? "Open Settings" : (prompter.locationGranted ? "Continue" : "Allow Location Access") + prompter.locationDenied ? String(localized: "Open Settings", comment: "Open iOS Settings button") : (prompter.locationGranted ? String(localized: "Continue", comment: "Onboarding continue button") : String(localized: "Allow Location Access", comment: "Request location permission button")) ) { if prompter.locationDenied { openSettings() @@ -258,7 +259,7 @@ struct OnboardingView: View { } case 2: primaryButton( - prompter.notificationsDenied ? "Continue without alerts" : (prompter.notificationsGranted ? "Continue" : "Allow Notifications") + prompter.notificationsDenied ? String(localized: "Continue without alerts", comment: "Continue after notification permission denied") : (prompter.notificationsGranted ? String(localized: "Continue", comment: "Onboarding continue button") : String(localized: "Allow Notifications", comment: "Request notifications permission button")) ) { if prompter.notificationsDenied || prompter.notificationsGranted { page = 3 // decided either way — move on @@ -269,13 +270,13 @@ struct OnboardingView: View { } case 3: // No permission on this page — prices need none. Straight on. - primaryButton("Continue") { page = 4 } + primaryButton(String(localized: "Continue", comment: "Onboarding continue button")) { page = 4 } default: - primaryButton("Start Using FuelBoard") { finish() } + primaryButton(String(localized: "Start Using FuelBoard", comment: "Finish onboarding button")) { finish() } } } - private func primaryButton(_ title: LocalizedStringKey, action: @escaping () -> Void) -> some View { + private func primaryButton(_ title: String, action: @escaping () -> Void) -> some View { Button(action: action) { Text(title) .font(.headline) @@ -285,7 +286,7 @@ struct OnboardingView: View { .buttonStyle(.borderedProminent) } - private func featureRow(icon: String, text: LocalizedStringKey) -> some View { + private func featureRow(icon: String, text: String) -> some View { HStack(spacing: 12) { Image(systemName: icon) .font(.system(size: 20, weight: .semibold)) @@ -299,10 +300,10 @@ struct OnboardingView: View { .padding(.vertical, 10) } - private func permissionStatusLabel(granted: Bool, denied: Bool, deniedText: LocalizedStringKey) -> some View { + private func permissionStatusLabel(granted: Bool, denied: Bool, deniedText: String) -> some View { Group { if granted { - Label("Allowed", systemImage: "checkmark.circle.fill") + Label(String(localized: "Allowed", comment: "Permission granted status"), systemImage: "checkmark.circle.fill") .foregroundStyle(.green) } else if denied { Text(deniedText) @@ -311,7 +312,7 @@ struct OnboardingView: View { .multilineTextAlignment(.center) .padding(.horizontal, 32) } else { - Text("The system prompt will appear next.") + Text(String(localized: "The system prompt will appear next.", comment: "Permission prompt pending status")) .font(.footnote) .foregroundStyle(.secondary) }