From 44a8e7f42a86a79604d3fbb372081155e111f5c9 Mon Sep 17 00:00:00 2001 From: FuelBoard Contributor Date: Sat, 15 Aug 2026 14:44:18 +0100 Subject: [PATCH] Onboarding: prompts fire on forward-leave of their page, not arrival MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit b634c06 fired on page arrival, so swiping stacked Location + Notifications before a screen was read. Now each permission fires only when the user ADVANCES PAST its page (swipe forward or Continue) — the screen has been read by then. Forward-only (swiping back never fires), idempotent (already-determined → status refresh), so replay and auto-advance stay clean. Continue buttons are now pure navigation; denied paths unchanged. --- FuelBoard/OnboardingView.swift | 35 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/FuelBoard/OnboardingView.swift b/FuelBoard/OnboardingView.swift index 93e3aaf..8491ce0 100644 --- a/FuelBoard/OnboardingView.swift +++ b/FuelBoard/OnboardingView.swift @@ -54,15 +54,17 @@ struct OnboardingView: View { .onChange(of: prompter.notificationsGranted) { _, granted in if granted, page == 2 { page = 3 } } - // Fire the permission the moment its page comes into view — swiping - // must trigger the prompt exactly like tapping Continue does. Both - // request methods are idempotent: an already-determined permission - // just refreshes the status (no duplicate system alert), so this is - // safe for replay and for the auto-advance path. - .onChange(of: page) { _, newPage in - if newPage == 1 { + // Fire each permission when the user LEAVES its page by advancing + // forward (swipe or Continue) — by then the screen has been read. + // Never on arrival: a fast swipe can't stack prompts before a page + // is seen. Forward-only: swiping back never fires. Idempotent — + // an already-determined permission just refreshes, so the + // auto-advance path and replays stay clean (no duplicate alerts). + .onChange(of: page) { oldPage, newPage in + guard newPage > oldPage else { return } + if oldPage == 1 { prompter.requestLocation() - } else if newPage == 2 { + } else if oldPage == 2 { prompter.requestNotifications() } } @@ -246,12 +248,10 @@ struct OnboardingView: View { ) { if prompter.locationDenied { openSettings() - } else if !prompter.locationGranted { - // Safety net — the prompt already fired when this page - // came into view (see onChange(of: page)); re-requesting - // is a no-op unless the status is still undetermined. - prompter.requestLocation() } else { + // The prompt fires on LEAVING this page (onChange(of: + // page), forward advance only) — the description is on + // screen until the user moves on. page = 2 } } @@ -259,13 +259,8 @@ struct OnboardingView: View { primaryButton( prompter.notificationsDenied ? "Continue without alerts" : (prompter.notificationsGranted ? "Continue" : "Allow Notifications") ) { - if prompter.notificationsDenied { - page = 3 - } else if !prompter.notificationsGranted { - prompter.requestNotifications() - } else { - page = 3 - } + // Prompt fires on leaving this page (same rule as Location). + page = 3 } case 3: // No permission on this page — prices need none. Straight on.