From f100187aa0c313fbe7928947ce3f5ec57d6fea4d Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 28 Aug 2026 17:07:54 -0400 Subject: [PATCH] fix(currency-creation): pop the creation flow when a launch finishes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The launch screen is a fullScreenCover over the creation wizard, and its `dismissParentContainer` was wired to `router.dismissSheet()`. The creation flow is pushed onto the Wallet tab's stack, so there is no sheet to pop and that call returns early — both exits from CurrencyLaunchProcessingScreen, the finished handoff and the failure dismissal, did nothing. The screen hides the back button and disables interactive dismiss, so a user whose launch bill was claimed by someone else was left in the wizard with only its back chevron and force-quit the app. Pop the wizard's owning stack to root instead, matching the convert flow. The stack is named rather than resolved through `popToRoot()`'s topmost lookup, since HomeTabView clears `activeTabStack` while the cover is up. The wiring was correct when it landed in #213, where `.balance` was sheet-hosted and dismissing the sheet tore down the whole flow; #619 made `.balance` tab-hosted and this call site was missed. --- .../CurrencyCreationWizardScreen.swift | 18 +++++--- .../CurrencyCreationFlowDismissalTests.swift | 43 +++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 FlipcashTests/Navigation/CurrencyCreationFlowDismissalTests.swift diff --git a/Flipcash/Core/Screens/Main/Currency Creation/CurrencyCreationWizardScreen.swift b/Flipcash/Core/Screens/Main/Currency Creation/CurrencyCreationWizardScreen.swift index f3d1539a8..f62073e44 100644 --- a/Flipcash/Core/Screens/Main/Currency Creation/CurrencyCreationWizardScreen.swift +++ b/Flipcash/Core/Screens/Main/Currency Creation/CurrencyCreationWizardScreen.swift @@ -261,12 +261,7 @@ struct CurrencyCreationWizardScreen: View { paymentMint: context.paymentMint ) .environment(\.dismissParentContainer, { - // Sheet dismiss unmounts the wizard, taking the - // fullScreenCover with it as a single animation. - // Nilling the cover binding here would stage a separate - // cover-dismiss before the sheet animation; the @State - // is freed automatically when the wizard unmounts. - router.dismissSheet() + Self.dismissCreationFlow(router: router) }) } } @@ -282,6 +277,17 @@ struct CurrencyCreationWizardScreen: View { } } + /// Unwinds the whole creation flow once the launch cover is done with it — + /// both the finished handoff and the failure dismissal come through here. + /// + /// The flow is pushed onto the Wallet tab's stack, so it comes off by + /// popping that stack to its root. Naming the stack rather than using + /// `popToRoot()`'s topmost lookup keeps this working while the cover is up, + /// since the tab host clears `activeTabStack` when it disappears. + static func dismissCreationFlow(router: AppRouter) { + router.popToRoot(on: AppRouter.Destination.currencyCreationWizard.owningStack) + } + // MARK: - Navigation private func advance() { diff --git a/FlipcashTests/Navigation/CurrencyCreationFlowDismissalTests.swift b/FlipcashTests/Navigation/CurrencyCreationFlowDismissalTests.swift new file mode 100644 index 000000000..3c9a8dedd --- /dev/null +++ b/FlipcashTests/Navigation/CurrencyCreationFlowDismissalTests.swift @@ -0,0 +1,43 @@ +// +// CurrencyCreationFlowDismissalTests.swift +// FlipcashTests +// + +import SwiftUI +import Testing +import FlipcashCore +@testable import Flipcash + +@MainActor +@Suite("Currency Creation Flow Dismissal") +struct CurrencyCreationFlowDismissalTests { + + /// Pushes the flow the way the Wallet tile does: summary, then wizard. + private func routerInCreationFlow() -> AppRouter { + let router = AppRouter() + router.activeTabStack = .balance + router.push(.currencyCreationSummary) + router.push(.currencyCreationWizard) + return router + } + + @Test("Finishing the launch cover pops the creation flow off the Wallet stack") + func dismissCreationFlow_popsHostStack() { + let router = routerInCreationFlow() + + CurrencyCreationWizardScreen.dismissCreationFlow(router: router) + + #expect(router[.balance].isEmpty, "the wizard must not stay mounted under the dismissed cover") + } + + @Test("Unwinds even though the cover hid the tab host and cleared the active stack") + func dismissCreationFlow_withoutActiveTabStack_popsHostStack() { + let router = routerInCreationFlow() + // `HomeTabView.onDisappear` clears this while the fullScreenCover is up. + router.activeTabStack = nil + + CurrencyCreationWizardScreen.dismissCreationFlow(router: router) + + #expect(router[.balance].isEmpty, "the unwind must not depend on the tab host still being mounted") + } +}