From 4c222cdf55f833a0338de810f6fe0eb6dbe9ba36 Mon Sep 17 00:00:00 2001 From: FuelBoard Contributor Date: Wed, 19 Aug 2026 11:10:29 +0100 Subject: [PATCH] banner: key by value so tip fades out on dismiss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The banner sits in a single if-let slot; without an identity key, clearing a tip that was covering a network/offline banner swapped content in place with no transition (instantly vanishing). Keying the banner by its value (.id) makes every change — tip→network or tip→nil — run the shared removal transition, so the tip slides up and fades out when it auto-dismisses or is tapped. --- FuelBoard/ContentView.swift | 6 +++++- FuelBoard/SettingsView.swift | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/FuelBoard/ContentView.swift b/FuelBoard/ContentView.swift index 700b440..558fd04 100644 --- a/FuelBoard/ContentView.swift +++ b/FuelBoard/ContentView.swift @@ -5,7 +5,7 @@ import WidgetKit /// The single floating notification banner. Network/offline state and tip /// outcomes all funnel through ONE style and render via the same chrome in /// ContentView — never pushing layout, always overlaid. -enum AppBanner: Equatable { +enum AppBanner: Hashable { case offlineDump(date: String) case connectionProblem case tip(TipStore.TipOutcome) @@ -491,6 +491,10 @@ struct ContentView: View { // main content so it doesn't cover or block the list/pill // beneath it. floatingBanner(banner) + // Key by value so ANY change (tip→network, tip→nil) + // runs the removal transition (fade/slide-out) instead + // of silently swapping content in place. + .id(banner) .padding(.top, geo.safeAreaInsets.top + 10) .transition(.asymmetric( insertion: .move(edge: .top).combined(with: .opacity), diff --git a/FuelBoard/SettingsView.swift b/FuelBoard/SettingsView.swift index 9582da5..3f0744f 100644 --- a/FuelBoard/SettingsView.swift +++ b/FuelBoard/SettingsView.swift @@ -604,7 +604,7 @@ final class TipStore: ObservableObject { ] /// A banner outcome: the exact copy to show plus its icon + tint. - struct TipOutcome: Equatable { + struct TipOutcome: Hashable { let message: String let icon: String let tint: Color