Widget opens Maps via maps://; onboarding prompts after Continue; forced first fetch
- Widget tap-to-directions now uses the native maps:// scheme instead of http://maps.apple.com. The universal-link http URL failed to open from a widget and fell back to launching the containing app; maps:// opens Apple Maps directly for both small (whole widget) and medium (per row). - Onboarding: removed the Skip button (mandatory flow), and system prompts (location, notifications, local network) now fire only when the user taps Continue/Allow on each page — not when the page appears. Pages auto-advance once a permission is granted. - After 'Start Using FuelBoard' the first data fetch is forced, so a reinstall that leaves a recent lastRefresh in app-group defaults can no longer skip the fetch and leave the station list empty. 35 tests pass.
This commit is contained in:
@@ -182,13 +182,16 @@ struct ContentView: View {
|
||||
.onChange(of: showOnboarding) { _, showing in
|
||||
// After onboarding finishes (or the test re-run is dismissed),
|
||||
// begin foreground location tracking if permission allows, and
|
||||
// run the first data fetch (the relay probe during onboarding
|
||||
// already surfaced the Local Network prompt).
|
||||
// run the FIRST data fetch. This must be a forced refresh: a
|
||||
// reinstall may leave a recent `lastRefresh` in the app-group
|
||||
// defaults (which survive app deletion), which would make the
|
||||
// cache-gated refresh skip the fetch and leave the station list
|
||||
// empty on a brand-new install.
|
||||
if !showing, FuelStore.loadHasCompletedOnboarding() {
|
||||
locationManager.startForegroundTracking()
|
||||
monitor.update(stations: stations, favourites: refreshedFavourites,
|
||||
fuel: alertsFuel, radiusKM: alertsRadius)
|
||||
Task { await refresh() }
|
||||
Task { await refresh(force: true) }
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase) { _, newPhase in
|
||||
|
||||
@@ -18,15 +18,8 @@ struct OnboardingView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
// Top bar: skip + page dots
|
||||
// Top bar: page dots only — onboarding is mandatory, no skip.
|
||||
HStack {
|
||||
if page < totalPages - 1 {
|
||||
Button("Skip") { finish() }
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
} else {
|
||||
Color.clear.frame(width: 40, height: 20)
|
||||
}
|
||||
Spacer()
|
||||
HStack(spacing: 8) {
|
||||
ForEach(0..<totalPages, id: \.self) { index in
|
||||
@@ -36,7 +29,6 @@ struct OnboardingView: View {
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
Color.clear.frame(width: 40, height: 20) // balances Skip
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.top, 12)
|
||||
@@ -49,17 +41,23 @@ struct OnboardingView: View {
|
||||
donePage.tag(4)
|
||||
}
|
||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||
.onChange(of: page) { _, newPage in
|
||||
// Trigger each system prompt the moment its page appears.
|
||||
if newPage == 1 { prompter.requestLocation() }
|
||||
if newPage == 2 { prompter.requestNotifications() }
|
||||
if newPage == 3 { prompter.requestDataAccess() }
|
||||
}
|
||||
|
||||
bottomAction
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.bottom, 24)
|
||||
}
|
||||
// Auto-advance once the user grants a permission — the prompt itself
|
||||
// only fires when the Continue button is tapped (after reading the
|
||||
// page's description), never when the page merely appears.
|
||||
.onChange(of: prompter.locationGranted) { _, granted in
|
||||
if granted, page == 1 { page = 2 }
|
||||
}
|
||||
.onChange(of: prompter.notificationsGranted) { _, granted in
|
||||
if granted, page == 2 { page = 3 }
|
||||
}
|
||||
.onChange(of: prompter.dataGranted) { _, granted in
|
||||
if granted, page == 3 { page = 4 }
|
||||
}
|
||||
.background(
|
||||
LinearGradient(
|
||||
colors: [Color(.systemBackground), Color.accentColor.opacity(0.06)],
|
||||
@@ -263,9 +261,12 @@ struct OnboardingView: View {
|
||||
) {
|
||||
if prompter.locationDenied {
|
||||
openSettings()
|
||||
} else {
|
||||
} else if !prompter.locationGranted {
|
||||
// The system prompt fires HERE — after the user has read
|
||||
// the description and tapped Continue — not on page appear.
|
||||
prompter.requestLocation()
|
||||
if prompter.locationGranted { page = 2 }
|
||||
} else {
|
||||
page = 2
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
@@ -274,9 +275,10 @@ struct OnboardingView: View {
|
||||
) {
|
||||
if prompter.notificationsDenied {
|
||||
page = 3
|
||||
} else {
|
||||
} else if !prompter.notificationsGranted {
|
||||
prompter.requestNotifications()
|
||||
if prompter.notificationsGranted { page = 3 }
|
||||
} else {
|
||||
page = 3
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
@@ -286,8 +288,12 @@ struct OnboardingView: View {
|
||||
) {
|
||||
if prompter.dataDenied {
|
||||
openSettings()
|
||||
} else if prompter.dataGranted || !prompter.dataLoading {
|
||||
} else if prompter.dataGranted {
|
||||
page = 4
|
||||
} else if !prompter.dataLoading {
|
||||
// Fires the Local Network prompt + relay probe here,
|
||||
// after the user has read the page and tapped Continue.
|
||||
prompter.requestDataAccess()
|
||||
}
|
||||
}
|
||||
.disabled(prompter.dataLoading)
|
||||
|
||||
Reference in New Issue
Block a user